Skip to content

Commit af97dee

Browse files
committed
1 parent 1c0788f commit af97dee

File tree

9 files changed

+158
-45
lines changed

9 files changed

+158
-45
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@
7575
],
7676
"source": [
7777
"./dist/test/e2e/*.js",
78+
"./dist/test/normalizer/*.json",
7879
"./dist/src/*.js",
7980
"./test/resources/*.json"
8081
],

src/normalizer.ts

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,23 +14,23 @@ rules.set('Destructure unary types', schema => {
1414
return schema
1515
})
1616

17-
rules.set('Add empty `required` property if none is defined', schema => {
18-
if (willBeInterface(schema) && !('required' in schema)) {
17+
rules.set('Add empty `required` property if none is defined', (schema, rootSchema) => {
18+
if (stringify(schema) === stringify(rootSchema) && !('required' in schema)) {
1919
schema.required = []
2020
}
2121
return schema
2222
})
2323

24-
rules.set('Transform `required`=false to `required`=[]', schema => {
25-
if (willBeInterface(schema) && schema.required === false) {
24+
rules.set('Transform `required`=false to `required`=[]', (schema, rootSchema) => {
25+
if (stringify(schema) === stringify(rootSchema) && schema.required === false) {
2626
schema.required = []
2727
}
2828
return schema
2929
})
3030

3131
// TODO: default to empty schema (as per spec) instead
32-
rules.set('Default additionalProperties to true', schema => {
33-
if (willBeInterface(schema) && !('additionalProperties' in schema)) {
32+
rules.set('Default additionalProperties to true', (schema, rootSchema) => {
33+
if (stringify(schema) === stringify(rootSchema) && !('additionalProperties' in schema)) {
3434
schema.additionalProperties = true
3535
}
3636
return schema
@@ -51,7 +51,3 @@ export function normalize(schema: JSONSchema, filename: string): NormalizedJSONS
5151
})
5252
return _schema
5353
}
54-
55-
function willBeInterface(schema: JSONSchema) {
56-
return Boolean(schema.id || schema.title || schema.properties || schema.definitions)
57-
}

src/parser.ts

Lines changed: 22 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export function parse(
1010
schema: JSONSchema | JSONSchema4Type,
1111
rootSchema = schema as JSONSchema,
1212
keyName?: string,
13+
isSchema = true,
1314
processed = new Map<JSONSchema | JSONSchema4Type, AST>()
1415
): AST {
1516

@@ -28,8 +29,8 @@ export function parse(
2829
processed.set(schema, ast)
2930
const set = (_ast: AST) => Object.assign(ast, _ast)
3031

31-
return isSchema(schema)
32-
? parseNonLiteral(schema, rootSchema, keyName, keyNameFromDefinition, set, processed)
32+
return isSchema
33+
? parseNonLiteral(schema as SchemaSchema, rootSchema, keyName, keyNameFromDefinition, set, processed)
3334
: parseLiteral(schema, keyName, keyNameFromDefinition, set)
3435
}
3536

@@ -63,7 +64,7 @@ function parseNonLiteral(
6364
return set({
6465
comment: schema.description,
6566
keyName,
66-
params: schema.allOf!.map(_ => parse(_, rootSchema, undefined, processed)),
67+
params: schema.allOf!.map(_ => parse(_, rootSchema, undefined, true, processed)),
6768
standaloneName: schema.title || keyNameFromDefinition,
6869
type: 'INTERSECTION'
6970
})
@@ -78,7 +79,7 @@ function parseNonLiteral(
7879
return set({
7980
comment: schema.description,
8081
keyName,
81-
params: schema.anyOf!.map(_ => parse(_, rootSchema, undefined, processed)),
82+
params: schema.anyOf!.map(_ => parse(_, rootSchema, undefined, true, processed)),
8283
standaloneName: schema.title || keyNameFromDefinition,
8384
type: 'UNION'
8485
})
@@ -94,7 +95,7 @@ function parseNonLiteral(
9495
comment: schema.description,
9596
keyName,
9697
params: schema.enum!.map((_, n) => ({
97-
ast: parse(_, rootSchema, undefined, processed),
98+
ast: parse(_, rootSchema, undefined, false, processed),
9899
keyName: schema.tsEnumNames![n]
99100
})),
100101
standaloneName: keyName!,
@@ -133,7 +134,7 @@ function parseNonLiteral(
133134
return set({
134135
comment: schema.description,
135136
keyName,
136-
params: schema.oneOf!.map(_ => parse(_, rootSchema, undefined, processed)),
137+
params: schema.oneOf!.map(_ => parse(_, rootSchema, undefined, true, processed)),
137138
standaloneName: schema.title || keyNameFromDefinition,
138139
type: 'UNION'
139140
})
@@ -151,15 +152,15 @@ function parseNonLiteral(
151152
return set({
152153
comment: schema.description,
153154
keyName,
154-
params: schema.items.map(_ => parse(_, rootSchema, undefined, processed)),
155+
params: schema.items.map(_ => parse(_, rootSchema, undefined, true, processed)),
155156
standaloneName: schema.title || keyNameFromDefinition,
156157
type: 'TUPLE'
157158
})
158159
} else {
159160
return set({
160161
comment: schema.description,
161162
keyName,
162-
params: parse(schema.items!, rootSchema, undefined, processed),
163+
params: parse(schema.items!, rootSchema, undefined, true, processed),
163164
standaloneName: schema.title || keyNameFromDefinition,
164165
type: 'ARRAY'
165166
})
@@ -168,15 +169,15 @@ function parseNonLiteral(
168169
return set({
169170
comment: schema.description,
170171
keyName,
171-
params: (schema.type as JSONSchema4TypeName[]).map(_ => parse({ required: [], type: _ }, rootSchema, undefined, processed)),
172+
params: (schema.type as JSONSchema4TypeName[]).map(_ => parse({ type: _ }, rootSchema, undefined, true, processed)),
172173
standaloneName: schema.title || keyNameFromDefinition,
173174
type: 'UNION'
174175
})
175176
case 'UNNAMED_ENUM':
176177
return set({
177178
comment: schema.description,
178179
keyName,
179-
params: schema.enum!.map(_ => parse(_, rootSchema, undefined, processed)),
180+
params: schema.enum!.map(_ => parse(_, rootSchema, undefined, false, processed)),
180181
standaloneName: schema.title || keyNameFromDefinition,
181182
type: 'UNION'
182183
})
@@ -217,7 +218,7 @@ function parseSchema(
217218
): TInterfaceParam[] {
218219

219220
const asts = map(schema.properties, (value, key: string) => ({
220-
ast: parse(value, rootSchema, key, processed),
221+
ast: parse(value, rootSchema, key, true, processed),
221222
isRequired: includes(schema.required || [], key),
222223
keyName: key
223224
}))
@@ -239,7 +240,7 @@ function parseSchema(
239240
// defined via index signatures are already optional
240241
default:
241242
return asts.concat({
242-
ast: parse(schema.additionalProperties, rootSchema, '[k: string]', processed),
243+
ast: parse(schema.additionalProperties, rootSchema, '[k: string]', true, processed),
243244
isRequired: true,
244245
keyName: '[k: string]'
245246
})
@@ -251,23 +252,27 @@ type Definitions = { [k: string]: JSONSchema }
251252
/**
252253
* TODO: Memoize
253254
*/
254-
function getDefinitions(schema: JSONSchema, processed = new Set<JSONSchema>()): Definitions {
255+
function getDefinitions(
256+
schema: JSONSchema,
257+
isSchema = true,
258+
processed = new Set<JSONSchema>()
259+
): Definitions {
255260
if (processed.has(schema)) {
256261
return {}
257262
}
258263
processed.add(schema)
259264
if (Array.isArray(schema)) {
260265
return schema.reduce((prev, cur) => ({
261266
...prev,
262-
...getDefinitions(cur, processed)
267+
...getDefinitions(cur, false, processed)
263268
}), {})
264269
}
265270
if (isPlainObject(schema)) {
266271
return {
267-
...(hasDefinitions(schema) ? schema.definitions! : {}),
272+
...(isSchema && hasDefinitions(schema) ? schema.definitions : {}),
268273
...Object.keys(schema).reduce<Definitions>((prev, cur) => ({
269274
...prev,
270-
...getDefinitions(schema[cur], processed)
275+
...getDefinitions(schema[cur], false, processed)
271276
}), {})
272277
}
273278
}
@@ -278,11 +283,5 @@ function getDefinitions(schema: JSONSchema, processed = new Set<JSONSchema>()):
278283
* TODO: Reduce rate of false positives
279284
*/
280285
function hasDefinitions(schema: JSONSchema): schema is JSONSchemaWithDefinitions {
281-
return isPlainObject(schema)
282-
&& 'definitions' in schema
283-
&& Object.keys(schema.definitions).every(_ => isPlainObject(schema.definitions![_]))
284-
}
285-
286-
function isSchema(schema: JSONSchema | JSONSchema4Type): schema is JSONSchema {
287-
return isPlainObject(schema)
286+
return 'definitions' in schema
288287
}

test/e2e/deep.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
export const input = {
2+
type: 'object',
3+
properties: {
4+
foo: {
5+
type: 'object',
6+
oneOf: [
7+
{
8+
oneOf: [
9+
{ type: 'number' },
10+
{ $ref: '#/definitions/foo' },
11+
{ $ref: '#/definitions/bar' },
12+
{
13+
properties: {
14+
baz: { type: 'number' }
15+
}
16+
}
17+
]
18+
},
19+
{ $ref: '#/definitions/bar' }
20+
]
21+
}
22+
},
23+
definitions: {
24+
foo: {
25+
properties: {
26+
a: { type: 'string' },
27+
b: { type: 'integer' }
28+
},
29+
additionalProperties: false,
30+
required: ['a', 'b']
31+
},
32+
bar: {
33+
properties: {
34+
a: { type: 'string' }
35+
},
36+
required: ['a', 'b']
37+
}
38+
},
39+
required: ['foo'],
40+
additionalProperties: false
41+
}
42+
43+
export const output = `export interface Deep {
44+
foo: ((number | Foo | Bar | {
45+
baz?: number;
46+
[k: string]: any;
47+
}) | Bar);
48+
}
49+
export interface Foo {
50+
a: string;
51+
b: number;
52+
}
53+
export interface Bar {
54+
a: string;
55+
[k: string]: any;
56+
}
57+
`

test/e2e/reservedWords.1.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
export const input = {
2+
definitions: {
3+
definitions: {
4+
type: 'integer'
5+
},
6+
properties: {
7+
type: 'string'
8+
}
9+
},
10+
properties: {
11+
additionalProperties: {
12+
items: {
13+
type: 'number'
14+
},
15+
type: 'array'
16+
},
17+
definitions: {
18+
type: 'number'
19+
},
20+
properties: {
21+
type: 'boolean'
22+
}
23+
},
24+
type: 'object'
25+
}
26+
27+
export const output = `export interface ReservedWords {
28+
additionalProperties?: number[];
29+
definitions?: number;
30+
properties?: boolean;
31+
[k: string]: any;
32+
}
33+
`

test/e2e/reservedWords.2.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
export const input = {
2+
type: 'object',
3+
properties: {
4+
definitions: {
5+
$ref: '#/definitions/definitions'
6+
}
7+
},
8+
definitions: {
9+
definitions: {
10+
$ref: '#/definitions/schema'
11+
},
12+
schema: {
13+
type: 'object',
14+
properties: {
15+
additionalProperties: {
16+
anyOf: [
17+
{
18+
$ref: '#/definitions/schema'
19+
}
20+
]
21+
}
22+
}
23+
}
24+
}
25+
}
26+
27+
export const output = `export interface ReservedWords {
28+
definitions?: Definitions;
29+
[k: string]: any;
30+
}
31+
export interface Definitions {
32+
additionalProperties?: Definitions;
33+
[k: string]: any;
34+
}
35+
`

test/normalizer/addEmptyRequiredProperty.json

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,7 @@
1717
"properties": {
1818
"a": {
1919
"type": "integer",
20-
"id": "a",
21-
"required": [],
22-
"additionalProperties": true
20+
"id": "a"
2321
}
2422
},
2523
"additionalProperties": true,

test/normalizer/defaultAdditionalProperties.json

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,7 @@
1717
"properties": {
1818
"a": {
1919
"type": "integer",
20-
"id": "a",
21-
"required": [],
22-
"additionalProperties": true
20+
"id": "a"
2321
}
2422
},
2523
"required": [],

test/normalizer/destructureUnaryTypes.json

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,17 +24,13 @@
2424
"definitions": {
2525
"a": {
2626
"type": "integer",
27-
"id": "a",
28-
"required": [],
29-
"additionalProperties": true
27+
"id": "a"
3028
}
3129
},
3230
"properties": {
3331
"b": {
3432
"type": "string",
35-
"id": "b",
36-
"required": [],
37-
"additionalProperties": true
33+
"id": "b"
3834
}
3935
},
4036
"additionalProperties": true,

0 commit comments

Comments
 (0)