Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Paths: prevent infinite recursion #891

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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",
Emiyaaaaa marked this conversation as resolved.
Show resolved Hide resolved
"@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);