-
-
Notifications
You must be signed in to change notification settings - Fork 548
/
readonly-deep.ts
140 lines (124 loc) · 4.71 KB
/
readonly-deep.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
import {expectType, expectAssignable} from 'tsd';
import type {Opaque, tag} from '../source/tagged';
import type {ReadonlyDeep, ReadonlyObjectDeep} from '../source/readonly-deep';
import type {JsonValue} from '../source/basic';
type Overloaded = {
(foo: number): string;
(foo: string, bar: number): number;
};
type Namespace = {
(foo: number): string;
baz: boolean[];
};
type NamespaceWithOverload = Overloaded & {
baz: boolean[];
};
type OpaqueObjectData = {a: number[]} | {b: string};
type OpaqueObject = Opaque<OpaqueObjectData, {token: unknown}>;
type ReadonlyJsonValue =
| {readonly [k: string]: ReadonlyJsonValue}
| readonly ReadonlyJsonValue[]
| number
| string
| boolean
| null;
class ClassA {
foo = 1;
}
const data = {
object: {
foo: 'bar',
},
constructor: ClassA,
fn: (_: string) => true,
fnWithOverload: ((_: number) => 'foo') as Overloaded,
namespace: {} as unknown as Namespace,
namespaceWithOverload: {} as unknown as NamespaceWithOverload,
string: 'foo',
number: 1,
boolean: false,
symbol: Symbol('test'),
date: new Date(),
regExp: /.*/,
null: null,
undefined: undefined, // eslint-disable-line object-shorthand
map: new Map<string, string>(),
set: new Set<string>(),
array: ['foo'],
emptyTuple: [] as [],
singleItemTuple: ['foo'] as ['foo'],
multiItemTuple: [{a: ''}, {b: 4}, {c: ''}] as [{a: string}, {b: number}, {c: string}],
trailingSpreadTuple: ['foo', 1] as [string, ...number[]],
leadingSpreadTuple: ['foo', 1] as [...string[], number],
readonlyMap: new Map<string, string>() as ReadonlyMap<string, string>,
readonlySet: new Set<string>() as ReadonlySet<string>,
readonlyArray: ['foo'] as readonly string[],
readonlyTuple: ['foo'] as const,
json: [{x: true}] as JsonValue,
opaqueObj: {a: [3]} as OpaqueObject, // eslint-disable-line @typescript-eslint/consistent-type-assertions
};
const readonlyData: ReadonlyDeep<typeof data> = data;
readonlyData.fn('foo');
readonlyData.fnWithOverload(1);
readonlyData.fnWithOverload('', 1);
expectType<typeof ClassA>(readonlyData.constructor);
const instance = new readonlyData.constructor();
instance.foo = 2; // Constructor is not made readonly
// @ts-expect-error
readonlyData.string = 'bar';
expectType<{readonly foo: string}>(readonlyData.object);
expectType<string>(readonlyData.string);
expectType<number>(readonlyData.number);
expectType<boolean>(readonlyData.boolean);
expectType<symbol>(readonlyData.symbol);
expectType<null>(readonlyData.null);
expectType<undefined>(readonlyData.undefined);
expectType<Date>(readonlyData.date);
expectType<RegExp>(readonlyData.regExp);
expectType<Readonly<ReadonlyMap<string, string>>>(readonlyData.map);
expectType<Readonly<ReadonlySet<string>>>(readonlyData.set);
expectType<readonly string[]>(readonlyData.array);
expectType<readonly []>(readonlyData.emptyTuple);
expectType<readonly ['foo']>(readonlyData.singleItemTuple);
expectType<readonly [string, ...number[]]>(readonlyData.trailingSpreadTuple);
expectType<readonly [...string[], number]>(readonlyData.leadingSpreadTuple);
expectType<readonly [{readonly a: string}, {readonly b: number}, {readonly c: string}]>(readonlyData.multiItemTuple);
expectType<Readonly<ReadonlyMap<string, string>>>(readonlyData.readonlyMap);
expectType<Readonly<ReadonlySet<string>>>(readonlyData.readonlySet);
expectType<readonly string[]>(readonlyData.readonlyArray);
expectType<readonly ['foo']>(readonlyData.readonlyTuple);
expectAssignable<ReadonlyJsonValue>(readonlyData.json);
expectAssignable<Opaque<ReadonlyDeep<OpaqueObjectData>, ReadonlyDeep<OpaqueObject[typeof tag]>>>(readonlyData.opaqueObj);
expectType<((foo: number) => string) & ReadonlyObjectDeep<Namespace>>(readonlyData.namespace);
expectType<string>(readonlyData.namespace(1));
expectType<readonly boolean[]>(readonlyData.namespace.baz);
// These currently aren't readonly due to TypeScript limitations.
// @see https://github.com/microsoft/TypeScript/issues/29732
expectType<NamespaceWithOverload>(readonlyData.namespaceWithOverload);
expectType<string>(readonlyData.namespaceWithOverload(1));
expectType<number>(readonlyData.namespaceWithOverload('foo', 1));
expectType<boolean[]>(readonlyData.namespaceWithOverload.baz);
// Test void
type VoidType = {
foo: void;
bar: string | void;
};
type VoidTypeExpected = {
readonly foo: void;
readonly bar: string | void;
};
declare const voidType: ReadonlyDeep<VoidType>;
expectType<VoidTypeExpected>(voidType);
// Standalone tests
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
const readonlyNamespace = {} as ReadonlyDeep<{
(foo: number): string;
baz: boolean[];
}>;
expectType<((foo: number) => string) & {
readonly baz: readonly boolean[];
}>(readonlyNamespace);
expectAssignable<{
(foo: number): string;
readonly baz: readonly boolean[];
}>(readonlyNamespace);