From 7d4e875b69ce6179eef4019f2c9a961cd89108d8 Mon Sep 17 00:00:00 2001 From: Haozheng Li Date: Sun, 16 Jun 2024 04:32:28 +0800 Subject: [PATCH 1/3] `Paths`: Prevent infinite recursion (#891) --- package.json | 1 + source/paths.d.ts | 22 ++++++++++++++-------- test-d/paths.ts | 14 ++++++++++++-- 3 files changed, 27 insertions(+), 10 deletions(-) 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); From c570ec2c40b8265e66028a3029be8ea5c9bb2d18 Mon Sep 17 00:00:00 2001 From: Grigoris Christainas Date: Sat, 15 Jun 2024 23:33:03 +0300 Subject: [PATCH 2/3] `Schema`: Fix handling of arrays (#887) --- source/schema.d.ts | 24 ++++++++++++------------ test-d/schema.ts | 37 +++++++++++++++++++++---------------- 2 files changed, 33 insertions(+), 28 deletions(-) diff --git a/source/schema.d.ts b/source/schema.d.ts index 364192f95..2730f8e7e 100644 --- a/source/schema.d.ts +++ b/source/schema.d.ts @@ -47,25 +47,25 @@ export type Schema = ObjectType extends string ? ValueType : ObjectType extends ReadonlySet ? ValueType - : ObjectType extends readonly unknown[] - ? ValueType - : ObjectType extends unknown[] + : ObjectType extends Array + ? Array> + : ObjectType extends (...arguments_: unknown[]) => unknown ? ValueType - : ObjectType extends (...arguments_: unknown[]) => unknown + : ObjectType extends Date ? ValueType - : ObjectType extends Date + : ObjectType extends Function ? ValueType - : ObjectType extends Function + : ObjectType extends RegExp ? ValueType - : ObjectType extends RegExp - ? ValueType - : ObjectType extends object - ? SchemaObject - : ValueType; + : ObjectType extends object + ? SchemaObject + : ValueType; /** Same as `Schema`, but accepts only `object`s as inputs. Internal helper for `Schema`. */ type SchemaObject = { - [KeyType in keyof ObjectType]: Schema | K; + [KeyType in keyof ObjectType]: ObjectType[KeyType] extends readonly unknown[] | unknown[] + ? Schema + : Schema | K; }; diff --git a/test-d/schema.ts b/test-d/schema.ts index 3200ea78c..8bcb44ba7 100644 --- a/test-d/schema.ts +++ b/test-d/schema.ts @@ -14,6 +14,7 @@ const foo = { set: new Set(), array: ['foo'], tuple: ['foo'] as ['foo'], + objectArray: [{key: 'value'}], readonlyMap: new Map() as ReadonlyMap, readonlySet: new Set() as ReadonlySet, readonlyArray: ['foo'] as readonly string[], @@ -36,12 +37,13 @@ const fooSchema: FooSchema = { symbol: 'A', map: 'A', set: 'A', - array: 'A', - tuple: 'A', + array: ['A'], + tuple: ['A'], + objectArray: [{key: 'A'}], readonlyMap: 'A', readonlySet: 'A', - readonlyArray: 'A', - readonlyTuple: 'A', + readonlyArray: ['A'] as const, + readonlyTuple: ['A'] as const, regExp: 'A', }, }; @@ -60,12 +62,13 @@ expectType(barSchema.boolean); expectType(barSchema.symbol); expectType(barSchema.map); expectType(barSchema.set); -expectType(barSchema.array); -expectType(barSchema.tuple); +expectType(barSchema.array); +expectType(barSchema.tuple); +expectType>(barSchema.objectArray); expectType(barSchema.readonlyMap); expectType(barSchema.readonlySet); -expectType(barSchema.readonlyArray); -expectType(barSchema.readonlyTuple); +expectType(barSchema.readonlyArray); +expectType(barSchema.readonlyTuple); expectType(barSchema.regExp); type ComplexOption = { @@ -92,12 +95,13 @@ const complexFoo: ComplexSchema = { symbol: createComplexOption('readonly'), map: createComplexOption('readonly'), set: createComplexOption('readonly'), - array: createComplexOption('readonly'), - tuple: createComplexOption('readonly'), + array: [createComplexOption('readonly')], + tuple: [createComplexOption('readonly')], + objectArray: [{key: createComplexOption('readonly')}], readonlyMap: createComplexOption('readonly'), readonlySet: createComplexOption('readonly'), - readonlyArray: createComplexOption('readonly'), - readonlyTuple: createComplexOption('readonly'), + readonlyArray: [createComplexOption('readonly')] as const, + readonlyTuple: [createComplexOption('readonly')] as const, regExp: createComplexOption('readonly'), }, }; @@ -114,10 +118,11 @@ expectType(complexBarSchema.boolean); expectType(complexBarSchema.symbol); expectType(complexBarSchema.map); expectType(complexBarSchema.set); -expectType(complexBarSchema.array); -expectType(complexBarSchema.tuple); +expectType(complexBarSchema.array); +expectType(complexBarSchema.tuple); +expectType>(complexBarSchema.objectArray); expectType(complexBarSchema.readonlyMap); expectType(complexBarSchema.readonlySet); -expectType(complexBarSchema.readonlyArray); -expectType(complexBarSchema.readonlyTuple); +expectType(complexBarSchema.readonlyArray); +expectType(complexBarSchema.readonlyTuple); expectType(complexBarSchema.regExp); From 28efb2965cfb2078ffdd6c17da8129d4d99818e7 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Sat, 15 Jun 2024 23:42:27 +0200 Subject: [PATCH 3/3] 4.20.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 69dc9380a..e93be6e90 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "type-fest", - "version": "4.20.0", + "version": "4.20.1", "description": "A collection of essential TypeScript types", "license": "(MIT OR CC0-1.0)", "repository": "sindresorhus/type-fest",