Skip to content

Commit b3cc337

Browse files
authored
Merge pull request #1583 from erikbrinkman/snc
Add checks for strict null checks
2 parents 2123bf2 + 5cccbdf commit b3cc337

File tree

1 file changed

+63
-40
lines changed

1 file changed

+63
-40
lines changed

lib/types/json-schema.ts

Lines changed: 63 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
11
/* eslint-disable @typescript-eslint/no-empty-interface */
2-
export type SomeJSONSchema = JSONSchemaType<Known, true>
2+
type StrictNullChecksWrapper<Name extends string, Type> = undefined extends null
3+
? `strictNullChecks must be true in tsconfig to use ${Name}`
4+
: Type
35

4-
export type PartialSchema<T> = Partial<JSONSchemaType<T, true>>
6+
export type SomeJSONSchema = UncheckedJSONSchemaType<Known, true>
57

6-
type JSONType<T extends string, _partial extends boolean> = _partial extends true
8+
type UncheckedPartialSchema<T> = Partial<UncheckedJSONSchemaType<T, true>>
9+
10+
export type PartialSchema<T> = StrictNullChecksWrapper<"PartialSchema", UncheckedPartialSchema<T>>
11+
12+
type JSONType<T extends string, IsPartial extends boolean> = IsPartial extends true
713
? T | undefined
814
: T
915

@@ -23,22 +29,22 @@ interface StringKeywords {
2329
format?: string
2430
}
2531

26-
export type JSONSchemaType<T, _partial extends boolean = false> = (
32+
type UncheckedJSONSchemaType<T, IsPartial extends boolean> = (
2733
| // these two unions allow arbitrary unions of types
2834
{
29-
anyOf: readonly JSONSchemaType<T, _partial>[]
35+
anyOf: readonly UncheckedJSONSchemaType<T, IsPartial>[]
3036
}
3137
| {
32-
oneOf: readonly JSONSchemaType<T, _partial>[]
38+
oneOf: readonly UncheckedJSONSchemaType<T, IsPartial>[]
3339
}
3440
// this union allows for { type: (primitive)[] } style schemas
3541
| ({
3642
type: (T extends number
37-
? JSONType<"number" | "integer", _partial>
43+
? JSONType<"number" | "integer", IsPartial>
3844
: T extends string
39-
? JSONType<"string", _partial>
45+
? JSONType<"string", IsPartial>
4046
: T extends boolean
41-
? JSONType<"boolean", _partial>
47+
? JSONType<"boolean", IsPartial>
4248
: never)[]
4349
} & (T extends number
4450
? NumberKeywords
@@ -50,11 +56,11 @@ export type JSONSchemaType<T, _partial extends boolean = false> = (
5056
// this covers "normal" types; it's last so typescript looks to it first for errors
5157
| ((T extends number
5258
? {
53-
type: JSONType<"number" | "integer", _partial>
59+
type: JSONType<"number" | "integer", IsPartial>
5460
} & NumberKeywords
5561
: T extends string
5662
? {
57-
type: JSONType<"string", _partial>
63+
type: JSONType<"string", IsPartial>
5864
} & StringKeywords
5965
: T extends boolean
6066
? {
@@ -63,17 +69,17 @@ export type JSONSchemaType<T, _partial extends boolean = false> = (
6369
: T extends [any, ...any[]]
6470
? {
6571
// JSON AnySchema for tuple
66-
type: JSONType<"array", _partial>
72+
type: JSONType<"array", IsPartial>
6773
items: {
68-
readonly [K in keyof T]-?: JSONSchemaType<T[K]> & Nullable<T[K]>
74+
readonly [K in keyof T]-?: UncheckedJSONSchemaType<T[K], false> & Nullable<T[K]>
6975
} & {length: T["length"]}
7076
minItems: T["length"]
7177
} & ({maxItems: T["length"]} | {additionalItems: false})
7278
: T extends readonly any[]
7379
? {
74-
type: JSONType<"array", _partial>
75-
items: JSONSchemaType<T[0]>
76-
contains?: PartialSchema<T[0]>
80+
type: JSONType<"array", IsPartial>
81+
items: UncheckedJSONSchemaType<T[0], false>
82+
contains?: UncheckedPartialSchema<T[0]>
7783
minItems?: number
7884
maxItems?: number
7985
minContains?: number
@@ -87,62 +93,79 @@ export type JSONSchemaType<T, _partial extends boolean = false> = (
8793
// "required" is not optional because it is often forgotten
8894
// "properties" are optional for more concise dictionary schemas
8995
// "patternProperties" and can be only used with interfaces that have string index
90-
type: JSONType<"object", _partial>
91-
additionalProperties?: boolean | JSONSchemaType<T[string]>
92-
unevaluatedProperties?: boolean | JSONSchemaType<T[string]>
93-
properties?: _partial extends true ? Partial<PropertiesSchema<T>> : PropertiesSchema<T>
94-
patternProperties?: {[Pattern in string]?: JSONSchemaType<T[string]>}
95-
propertyNames?: Omit<JSONSchemaType<string>, "type"> & {type?: "string"}
96-
dependencies?: {[K in keyof T]?: Readonly<(keyof T)[]> | PartialSchema<T>}
96+
type: JSONType<"object", IsPartial>
97+
additionalProperties?: boolean | UncheckedJSONSchemaType<T[string], false>
98+
unevaluatedProperties?: boolean | UncheckedJSONSchemaType<T[string], false>
99+
properties?: IsPartial extends true
100+
? Partial<UncheckedPropertiesSchema<T>>
101+
: UncheckedPropertiesSchema<T>
102+
patternProperties?: {[Pattern in string]?: UncheckedJSONSchemaType<T[string], false>}
103+
propertyNames?: Omit<UncheckedJSONSchemaType<string, false>, "type"> & {type?: "string"}
104+
dependencies?: {[K in keyof T]?: Readonly<(keyof T)[]> | UncheckedPartialSchema<T>}
97105
dependentRequired?: {[K in keyof T]?: Readonly<(keyof T)[]>}
98-
dependentSchemas?: {[K in keyof T]?: PartialSchema<T>}
106+
dependentSchemas?: {[K in keyof T]?: UncheckedPartialSchema<T>}
99107
minProperties?: number
100108
maxProperties?: number
101109
} & (// "required" type does not guarantee that all required properties
102110
// are listed it only asserts that optional cannot be listed.
103111
// "required" is not necessary if it's a non-partial type with no required keys
104-
_partial extends true
112+
IsPartial extends true
105113
? {required: Readonly<(keyof T)[]>}
106-
: [RequiredMembers<T>] extends [never]
107-
? {required?: Readonly<RequiredMembers<T>[]>}
108-
: {required: Readonly<RequiredMembers<T>[]>})
114+
: [UncheckedRequiredMembers<T>] extends [never]
115+
? {required?: Readonly<UncheckedRequiredMembers<T>[]>}
116+
: {required: Readonly<UncheckedRequiredMembers<T>[]>})
109117
: T extends null
110118
? {
111119
nullable: true
112120
}
113121
: never) & {
114-
allOf?: Readonly<PartialSchema<T>[]>
115-
anyOf?: Readonly<PartialSchema<T>[]>
116-
oneOf?: Readonly<PartialSchema<T>[]>
117-
if?: PartialSchema<T>
118-
then?: PartialSchema<T>
119-
else?: PartialSchema<T>
120-
not?: PartialSchema<T>
122+
allOf?: Readonly<UncheckedPartialSchema<T>[]>
123+
anyOf?: Readonly<UncheckedPartialSchema<T>[]>
124+
oneOf?: Readonly<UncheckedPartialSchema<T>[]>
125+
if?: UncheckedPartialSchema<T>
126+
then?: UncheckedPartialSchema<T>
127+
else?: UncheckedPartialSchema<T>
128+
not?: UncheckedPartialSchema<T>
121129
})
122130
) & {
123131
[keyword: string]: any
124132
$id?: string
125133
$ref?: string
126134
$defs?: {
127-
[Key in string]?: JSONSchemaType<Known, true>
135+
[Key in string]?: UncheckedJSONSchemaType<Known, true>
128136
}
129137
definitions?: {
130-
[Key in string]?: JSONSchemaType<Known, true>
138+
[Key in string]?: UncheckedJSONSchemaType<Known, true>
131139
}
132140
}
133141

142+
export type JSONSchemaType<T> = StrictNullChecksWrapper<
143+
"JSONSchemaType",
144+
UncheckedJSONSchemaType<T, false>
145+
>
146+
134147
type Known = KnownRecord | [Known, ...Known[]] | Known[] | number | string | boolean | null
135148

136149
interface KnownRecord extends Record<string, Known> {}
137150

138-
export type PropertiesSchema<T> = {
139-
[K in keyof T]-?: (JSONSchemaType<T[K]> & Nullable<T[K]>) | {$ref: string}
151+
type UncheckedPropertiesSchema<T> = {
152+
[K in keyof T]-?: (UncheckedJSONSchemaType<T[K], false> & Nullable<T[K]>) | {$ref: string}
140153
}
141154

142-
export type RequiredMembers<T> = {
155+
export type PropertiesSchema<T> = StrictNullChecksWrapper<
156+
"PropertiesSchema",
157+
UncheckedPropertiesSchema<T>
158+
>
159+
160+
type UncheckedRequiredMembers<T> = {
143161
[K in keyof T]-?: undefined extends T[K] ? never : K
144162
}[keyof T]
145163

164+
export type RequiredMembers<T> = StrictNullChecksWrapper<
165+
"RequiredMembers",
166+
UncheckedRequiredMembers<T>
167+
>
168+
146169
type Nullable<T> = undefined extends T
147170
? {
148171
nullable: true

0 commit comments

Comments
 (0)