Skip to content
This repository was archived by the owner on Nov 14, 2022. It is now read-only.

Commit c6676b6

Browse files
authored
Merge pull request bcherny#453 from bcherny/376
Fix cycles during emission (fix bcherny#376, fix bcherny#323)
2 parents a92d7c9 + 2ca6e50 commit c6676b6

File tree

11 files changed

+1482
-1125
lines changed

11 files changed

+1482
-1125
lines changed

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@
4646
},
4747
"homepage": "https://github.com/bcherny/json-schema-to-typescript#readme",
4848
"dependencies": {
49-
"@apidevtools/json-schema-ref-parser": "^9.0.9",
49+
"@apidevtools/json-schema-ref-parser": "https://github.com/bcherny/json-schema-ref-parser.git#984282d3",
5050
"@types/json-schema": "^7.0.11",
5151
"@types/lodash": "^4.14.182",
5252
"@types/prettier": "^2.6.1",

src/index.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -141,16 +141,16 @@ export async function compile(schema: JSONSchema4, name: string, options: Partia
141141
// Initial clone to avoid mutating the input
142142
const _schema = cloneDeep(schema)
143143

144-
const dereferenced = await dereference(_schema, _options)
144+
const {dereferencedPaths, dereferencedSchema} = await dereference(_schema, _options)
145145
if (process.env.VERBOSE) {
146-
if (isDeepStrictEqual(_schema, dereferenced)) {
146+
if (isDeepStrictEqual(_schema, dereferencedSchema)) {
147147
log('green', 'dereferencer', time(), '✅ No change')
148148
} else {
149-
log('green', 'dereferencer', time(), '✅ Result:', dereferenced)
149+
log('green', 'dereferencer', time(), '✅ Result:', dereferencedSchema)
150150
}
151151
}
152152

153-
const linked = link(dereferenced)
153+
const linked = link(dereferencedSchema)
154154
if (process.env.VERBOSE) {
155155
log('green', 'linker', time(), '✅ No change')
156156
}
@@ -164,7 +164,7 @@ export async function compile(schema: JSONSchema4, name: string, options: Partia
164164
log('green', 'validator', time(), '✅ No change')
165165
}
166166

167-
const normalized = normalize(linked, name, _options)
167+
const normalized = normalize(linked, dereferencedPaths, name, _options)
168168
log('yellow', 'normalizer', time(), '✅ Result:', normalized)
169169

170170
const parsed = parse(normalized, _options)

src/normalizer.ts

+40-7
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
11
import {JSONSchemaTypeName, LinkedJSONSchema, NormalizedJSONSchema, Parent} from './types/JSONSchema'
22
import {appendToDescription, escapeBlockComment, isSchemaLike, justName, toSafeString, traverse} from './utils'
33
import {Options} from './'
4-
5-
type Rule = (schema: LinkedJSONSchema, fileName: string, options: Options) => void
4+
import {DereferencedPaths} from './resolver'
5+
6+
type Rule = (
7+
schema: LinkedJSONSchema,
8+
fileName: string,
9+
options: Options,
10+
key: string | null,
11+
dereferencedPaths: DereferencedPaths
12+
) => void
613
const rules = new Map<string, Rule>()
714

815
function hasType(schema: LinkedJSONSchema, type: JSONSchemaTypeName) {
@@ -65,10 +72,31 @@ rules.set('Transform id to $id', (schema, fileName) => {
6572
}
6673
})
6774

68-
rules.set('Default top level $id', (schema, fileName) => {
69-
const isRoot = schema[Parent] === null
70-
if (isRoot && !schema.$id) {
75+
rules.set('Add an $id to anything that needs it', (schema, fileName, _options, _key, dereferencedPaths) => {
76+
if (!isSchemaLike(schema)) {
77+
return
78+
}
79+
80+
// Top-level schema
81+
if (!schema.$id && !schema[Parent]) {
7182
schema.$id = toSafeString(justName(fileName))
83+
return
84+
}
85+
86+
// Sub-schemas with references
87+
if (!isArrayType(schema) && !isObjectType(schema)) {
88+
return
89+
}
90+
91+
// We'll infer from $id and title downstream
92+
// TODO: Normalize upstream
93+
const dereferencedName = dereferencedPaths.get(schema)
94+
if (!schema.$id && !schema.title && dereferencedName) {
95+
schema.$id = toSafeString(justName(dereferencedName))
96+
}
97+
98+
if (dereferencedName) {
99+
dereferencedPaths.delete(schema)
72100
}
73101
})
74102

@@ -188,7 +216,12 @@ rules.set('Transform const to singleton enum', schema => {
188216
}
189217
})
190218

191-
export function normalize(rootSchema: LinkedJSONSchema, filename: string, options: Options): NormalizedJSONSchema {
192-
rules.forEach(rule => traverse(rootSchema, schema => rule(schema, filename, options)))
219+
export function normalize(
220+
rootSchema: LinkedJSONSchema,
221+
dereferencedPaths: DereferencedPaths,
222+
filename: string,
223+
options: Options
224+
): NormalizedJSONSchema {
225+
rules.forEach(rule => traverse(rootSchema, (schema, key) => rule(schema, filename, options, key, dereferencedPaths)))
193226
return rootSchema as NormalizedJSONSchema
194227
}

src/resolver.ts

+14-2
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,23 @@ import $RefParser = require('@apidevtools/json-schema-ref-parser')
22
import {JSONSchema} from './types/JSONSchema'
33
import {log} from './utils'
44

5+
export type DereferencedPaths = WeakMap<$RefParser.JSONSchemaObject, string>
6+
57
export async function dereference(
68
schema: JSONSchema,
79
{cwd, $refOptions}: {cwd: string; $refOptions: $RefParser.Options}
8-
): Promise<JSONSchema> {
10+
): Promise<{dereferencedPaths: DereferencedPaths; dereferencedSchema: JSONSchema}> {
911
log('green', 'dereferencer', 'Dereferencing input schema:', cwd, schema)
1012
const parser = new $RefParser()
11-
return parser.dereference(cwd, schema as any, $refOptions) as any // TODO: fix types
13+
const dereferencedPaths: DereferencedPaths = new WeakMap()
14+
const dereferencedSchema = await parser.dereference(cwd, schema as any, {
15+
...$refOptions,
16+
dereference: {
17+
...$refOptions.dereference,
18+
onDereference($ref, schema) {
19+
dereferencedPaths.set(schema, $ref)
20+
}
21+
}
22+
}) as any // TODO: fix types
23+
return {dereferencedPaths, dereferencedSchema}
1224
}

src/typesOfSchema.ts

+1
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ const matchers: Record<SchemaType, (schema: JSONSchema) => boolean> = {
6565
return 'enum' in schema && 'tsEnumNames' in schema
6666
},
6767
NAMED_SCHEMA(schema) {
68+
// 8.2.1. The presence of "$id" in a subschema indicates that the subschema constitutes a distinct schema resource within a single schema document.
6869
return '$id' in schema && ('patternProperties' in schema || 'properties' in schema)
6970
},
7071
NULL(schema) {

0 commit comments

Comments
 (0)