Skip to content

Commit 44793a4

Browse files
committed
[1/x] generalize linker to annotator
1 parent 6fc74b3 commit 44793a4

File tree

14 files changed

+150
-145
lines changed

14 files changed

+150
-145
lines changed

ARCHITECTURE.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@ TODO use an external validation library
88

99
Resolves referenced schemas (in the file, on the local filesystem, or over the network).
1010

11-
#### 3. Linker
11+
#### 3. Annotator
1212

13-
Adds links back from each node in a schema to its parent (available via the `Parent` symbol on each node), for convenience.
13+
Annotates the JSON schema with metadata that will be used later on. For example, this step adds links back from each node in a schema to its parent (available via the `Metadata` symbol on each node), for convenience.
1414

1515
#### 4. Normalizer
1616

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ See [server demo](example) and [browser demo](https://github.com/bcherny/json-sc
134134
|-|-|-|-|
135135
| additionalProperties | boolean | `true` | Default value for `additionalProperties`, when it is not explicitly set |
136136
| bannerComment | string | `"/* eslint-disable */\n/**\n* This file was automatically generated by json-schema-to-typescript.\n* DO NOT MODIFY IT BY HAND. Instead, modify the source JSON Schema file,\n* and run json-schema-to-typescript to regenerate this file.\n*/"` | Disclaimer comment prepended to the top of each generated file |
137-
| customName | `(LinkedJSONSchema, string \| undefined) => string \| undefined` | `undefined` | Custom function to provide a type name for a given schema
137+
| customName | `(JSONSchema, string \| undefined) => string \| undefined` | `undefined` | Custom function to provide a type name for a given schema
138138
| cwd | string | `process.cwd()` | Root directory for resolving [`$ref`](https://tools.ietf.org/id/draft-pbryan-zyp-json-ref-03.html)s |
139139
| declareExternallyReferenced | boolean | `true` | Declare external schemas referenced via `$ref`? |
140140
| enableConstEnums | boolean | `true` | Prepend enums with [`const`](https://www.typescriptlang.org/docs/handbook/enums.html#computed-and-constant-members)? |

src/annotator.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import {isPlainObject} from 'lodash'
2+
import {DereferencedPaths} from './resolver'
3+
import {AnnotatedJSONSchema, JSONSchema, Parent, isAnnotated} from './types/JSONSchema'
4+
5+
/**
6+
* Traverses over the schema, assigning to each
7+
* node metadata that will be used downstream.
8+
*/
9+
export function annotate(
10+
schema: JSONSchema,
11+
dereferencedPaths: DereferencedPaths,
12+
parent: JSONSchema | null = null,
13+
): AnnotatedJSONSchema {
14+
if (!Array.isArray(schema) && !isPlainObject(schema)) {
15+
return schema as AnnotatedJSONSchema
16+
}
17+
18+
// Handle cycles
19+
if (isAnnotated(schema)) {
20+
return schema
21+
}
22+
23+
// Add a reference to this schema's parent
24+
Object.defineProperty(schema, Parent, {
25+
enumerable: false,
26+
value: parent,
27+
writable: false,
28+
})
29+
30+
// Arrays
31+
if (Array.isArray(schema)) {
32+
schema.forEach(child => annotate(child, dereferencedPaths, schema))
33+
}
34+
35+
// Objects
36+
for (const key in schema) {
37+
annotate(schema[key], dereferencedPaths, schema)
38+
}
39+
40+
return schema as AnnotatedJSONSchema
41+
}

src/index.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ import {dereference} from './resolver'
1313
import {error, stripExtension, Try, log, parseFileAsJSONSchema} from './utils'
1414
import {validate} from './validator'
1515
import {isDeepStrictEqual} from 'util'
16-
import {link} from './linker'
16+
import {annotate} from './annotator'
1717
import {validateOptions} from './optionValidator'
18-
import {JSONSchema as LinkedJSONSchema} from './types/JSONSchema'
18+
import {AnnotatedJSONSchema} from './types/JSONSchema'
1919

2020
export {EnumJSONSchema, JSONSchema, NamedEnumJSONSchema, CustomTypeJSONSchema} from './types/JSONSchema'
2121

@@ -35,7 +35,7 @@ export interface Options {
3535
/**
3636
* Custom function to provide a type name for a given schema
3737
*/
38-
customName?: (schema: LinkedJSONSchema, keyNameFromDefinition: string | undefined) => string | undefined
38+
customName?: (schema: AnnotatedJSONSchema, keyNameFromDefinition: string | undefined) => string | undefined
3939
/**
4040
* Root directory for resolving [`$ref`](https://tools.ietf.org/id/draft-pbryan-zyp-json-ref-03.html)s.
4141
*/
@@ -159,12 +159,12 @@ export async function compile(schema: JSONSchema4, name: string, options: Partia
159159
}
160160
}
161161

162-
const linked = link(dereferencedSchema)
162+
const annotated = annotate(dereferencedSchema, dereferencedPaths)
163163
if (process.env.VERBOSE) {
164-
log('green', 'linker', time(), '✅ No change')
164+
log('green', 'annotater', time(), '✅ No change')
165165
}
166166

167-
const errors = validate(linked, name)
167+
const errors = validate(annotated, name)
168168
if (errors.length) {
169169
errors.forEach(_ => error(_))
170170
throw new ValidationError()
@@ -173,7 +173,7 @@ export async function compile(schema: JSONSchema4, name: string, options: Partia
173173
log('green', 'validator', time(), '✅ No change')
174174
}
175175

176-
const normalized = normalize(linked, dereferencedPaths, name, _options)
176+
const normalized = normalize(annotated, dereferencedPaths, name, _options)
177177
log('yellow', 'normalizer', time(), '✅ Result:', normalized)
178178

179179
const parsed = parse(normalized, _options)

src/linker.ts

Lines changed: 0 additions & 37 deletions
This file was deleted.

src/normalizer.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,28 @@
1-
import {JSONSchemaTypeName, LinkedJSONSchema, NormalizedJSONSchema, Parent} from './types/JSONSchema'
1+
import {JSONSchemaTypeName, AnnotatedJSONSchema, NormalizedJSONSchema, JSONSchema, Parent} from './types/JSONSchema'
22
import {appendToDescription, escapeBlockComment, isSchemaLike, justName, toSafeString, traverse} from './utils'
33
import {Options} from './'
44
import {DereferencedPaths} from './resolver'
55
import {isDeepStrictEqual} from 'util'
66

77
type Rule = (
8-
schema: LinkedJSONSchema,
8+
schema: AnnotatedJSONSchema,
99
fileName: string,
1010
options: Options,
1111
key: string | null,
1212
dereferencedPaths: DereferencedPaths,
1313
) => void
1414
const rules = new Map<string, Rule>()
1515

16-
function hasType(schema: LinkedJSONSchema, type: JSONSchemaTypeName) {
16+
function hasType(schema: JSONSchema, type: JSONSchemaTypeName) {
1717
return schema.type === type || (Array.isArray(schema.type) && schema.type.includes(type))
1818
}
19-
function isObjectType(schema: LinkedJSONSchema) {
19+
function isObjectType(schema: JSONSchema) {
2020
return schema.properties !== undefined || hasType(schema, 'object') || hasType(schema, 'any')
2121
}
22-
function isArrayType(schema: LinkedJSONSchema) {
22+
function isArrayType(schema: JSONSchema) {
2323
return schema.items !== undefined || hasType(schema, 'array') || hasType(schema, 'any')
2424
}
25-
function isEnumTypeWithoutTsEnumNames(schema: LinkedJSONSchema) {
25+
function isEnumTypeWithoutTsEnumNames(schema: JSONSchema) {
2626
return schema.type === 'string' && schema.enum !== undefined && schema.tsEnumNames === undefined
2727
}
2828

@@ -232,7 +232,7 @@ rules.set('Add tsEnumNames to enum types', (schema, _, options) => {
232232
})
233233

234234
export function normalize(
235-
rootSchema: LinkedJSONSchema,
235+
rootSchema: AnnotatedJSONSchema,
236236
dereferencedPaths: DereferencedPaths,
237237
filename: string,
238238
options: Options,

src/types/JSONSchema.ts

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -42,41 +42,37 @@ export interface JSONSchema extends JSONSchema4 {
4242

4343
export const Parent = Symbol('Parent')
4444

45-
export interface LinkedJSONSchema extends JSONSchema {
46-
/**
47-
* A reference to this schema's parent node, for convenience.
48-
* `null` when this is the root schema.
49-
*/
50-
[Parent]: LinkedJSONSchema | null
45+
export interface AnnotatedJSONSchema extends JSONSchema {
46+
[Parent]: AnnotatedJSONSchema
5147

52-
additionalItems?: boolean | LinkedJSONSchema
53-
additionalProperties?: boolean | LinkedJSONSchema
54-
items?: LinkedJSONSchema | LinkedJSONSchema[]
48+
additionalItems?: boolean | AnnotatedJSONSchema
49+
additionalProperties?: boolean | AnnotatedJSONSchema
50+
items?: AnnotatedJSONSchema | AnnotatedJSONSchema[]
5551
definitions?: {
56-
[k: string]: LinkedJSONSchema
52+
[k: string]: AnnotatedJSONSchema
5753
}
5854
properties?: {
59-
[k: string]: LinkedJSONSchema
55+
[k: string]: AnnotatedJSONSchema
6056
}
6157
patternProperties?: {
62-
[k: string]: LinkedJSONSchema
58+
[k: string]: AnnotatedJSONSchema
6359
}
6460
dependencies?: {
65-
[k: string]: LinkedJSONSchema | string[]
61+
[k: string]: AnnotatedJSONSchema | string[]
6662
}
67-
allOf?: LinkedJSONSchema[]
68-
anyOf?: LinkedJSONSchema[]
69-
oneOf?: LinkedJSONSchema[]
70-
not?: LinkedJSONSchema
63+
allOf?: AnnotatedJSONSchema[]
64+
anyOf?: AnnotatedJSONSchema[]
65+
oneOf?: AnnotatedJSONSchema[]
66+
not?: AnnotatedJSONSchema
7167
}
7268

7369
/**
7470
* Normalized JSON schema.
7571
*
7672
* Note: `definitions` and `id` are removed by the normalizer. Use `$defs` and `$id` instead.
7773
*/
78-
export interface NormalizedJSONSchema extends Omit<LinkedJSONSchema, 'definitions' | 'id'> {
79-
[Parent]: NormalizedJSONSchema | null
74+
export interface NormalizedJSONSchema extends Omit<AnnotatedJSONSchema, 'definitions' | 'id'> {
75+
[Parent]: NormalizedJSONSchema
8076

8177
additionalItems?: boolean | NormalizedJSONSchema
8278
additionalProperties: boolean | NormalizedJSONSchema
@@ -134,14 +130,18 @@ export const getRootSchema = memoize((schema: NormalizedJSONSchema): NormalizedJ
134130
return getRootSchema(parent)
135131
})
136132

137-
export function isBoolean(schema: LinkedJSONSchema | JSONSchemaType): schema is boolean {
133+
export function isBoolean(schema: AnnotatedJSONSchema | JSONSchemaType): schema is boolean {
138134
return schema === true || schema === false
139135
}
140136

141-
export function isPrimitive(schema: LinkedJSONSchema | JSONSchemaType): schema is JSONSchemaType {
137+
export function isPrimitive(schema: AnnotatedJSONSchema | JSONSchemaType): schema is JSONSchemaType {
142138
return !isPlainObject(schema)
143139
}
144140

145141
export function isCompound(schema: JSONSchema): boolean {
146142
return Array.isArray(schema.type) || 'anyOf' in schema || 'oneOf' in schema
147143
}
144+
145+
export function isAnnotated(schema: JSONSchema): schema is AnnotatedJSONSchema {
146+
return schema.hasOwnProperty(Parent)
147+
}

0 commit comments

Comments
 (0)