Skip to content

Commit

Permalink
Paths: Prevent infinite recursion (#891)
Browse files Browse the repository at this point in the history
  • Loading branch information
Emiyaaaaa committed Jun 15, 2024
1 parent 6ef562a commit 7d4e875
Show file tree
Hide file tree
Showing 3 changed files with 27 additions and 10 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
22 changes: 14 additions & 8 deletions source/paths.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -45,22 +47,24 @@ open('listB.1'); // TypeError. Because listB only has one element.
@category Object
@category Array
*/
export type Paths<T> =
export type Paths<T> = Paths_<T>;

type Paths_<T, Depth extends number = 0> =
T extends NonRecursiveType | ReadonlyMap<unknown, unknown> | ReadonlySet<unknown>
? never
: IsAny<T> extends true
? never
: T extends UnknownArray
? number extends T['length']
// We need to handle the fixed and non-fixed index part of the array separately.
? InternalPaths<StaticPartOfArray<T>>
| InternalPaths<Array<VariablePartOfArray<T>[number]>>
: InternalPaths<T>
? InternalPaths<StaticPartOfArray<T>, Depth>
| InternalPaths<Array<VariablePartOfArray<T>[number]>, Depth>
: InternalPaths<T, Depth>
: T extends object
? InternalPaths<T>
? InternalPaths<T, Depth>
: never;

export type InternalPaths<_T, T = Required<_T>> =
export type InternalPaths<_T, Depth extends number = 0, T = Required<_T>> =
T extends EmptyObject | readonly []
? never
: {
Expand All @@ -71,8 +75,10 @@ export type InternalPaths<_T, T = Required<_T>> =
| Key
| ToString<Key>
| (
IsNever<Paths<T[Key]>> extends false
? `${Key}.${Paths<T[Key]>}`
LessThan<Depth, 15> extends true // Limit the depth to prevent infinite recursion
? IsNever<Paths_<T[Key], Sum<Depth, 1>>> extends false
? `${Key}.${Paths_<T[Key], Sum<Depth, 1>>}`
: never
: never
)
: never
Expand Down
14 changes: 12 additions & 2 deletions test-d/paths.ts
Original file line number Diff line number Diff line change
@@ -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);
Expand Down Expand Up @@ -98,3 +98,13 @@ expectType<number | `${number}` | `${number}.b` | `${number}.a`>(leadingSpreadTu

declare const leadingSpreadTuple1: Paths<[...Array<{a: string}>, {b: number}, {c: number}]>;
expectType<number | `${number}` | `${number}.b` | `${number}.c` | `${number}.a`>(leadingSpreadTuple1);

// Circularly references
type MyEntity = {
myOtherEntity?: MyOtherEntity;
};
type MyOtherEntity = {
myEntity?: MyEntity;
};
type MyEntityPaths = Paths<MyEntity>;
expectAssignable<string>({} as MyEntityPaths);

0 comments on commit 7d4e875

Please sign in to comment.