json-schema-to-typescript
Advanced tools
Comparing version 14.1.0 to 15.0.0
@@ -5,2 +5,6 @@ # Changelog | ||
## 15.0.0 | ||
- 62cc052 Fixed bug where intersection schemas didn't generate complete types. Improved output readability for intersection types (#603) | ||
## 14.1.0 | ||
@@ -7,0 +11,0 @@ |
@@ -6,2 +6,3 @@ "use strict"; | ||
const utils_1 = require("./utils"); | ||
const applySchemaTyping_1 = require("./applySchemaTyping"); | ||
const util_1 = require("util"); | ||
@@ -196,2 +197,17 @@ const rules = new Map(); | ||
}); | ||
// Precalculation of the schema types is necessary because the ALL_OF type | ||
// is implemented in a way that mutates the schema object. Detection of the | ||
// NAMED_SCHEMA type relies on the presence of the $id property, which is | ||
// hoisted to a parent schema object during the ALL_OF type implementation, | ||
// and becomes unavailable if the same schema is used in multiple places. | ||
// | ||
// Precalculation of the `ALL_OF` intersection schema is necessary because | ||
// the intersection schema needs to participate in the schema cache during | ||
// the parsing step, so it cannot be re-calculated every time the schema | ||
// is encountered. | ||
rules.set('Pre-calculate schema types and intersections', schema => { | ||
if (schema !== null && typeof schema === 'object') { | ||
(0, applySchemaTyping_1.applySchemaTyping)(schema); | ||
} | ||
}); | ||
function normalize(rootSchema, dereferencedPaths, filename, options) { | ||
@@ -198,0 +214,0 @@ rules.forEach(rule => (0, utils_1.traverse)(rootSchema, (schema, key) => rule(schema, filename, options, key, dereferencedPaths))); |
import { JSONSchema4Type } from 'json-schema'; | ||
import { Options } from './'; | ||
import { AST } from './types/AST'; | ||
import { NormalizedJSONSchema, SchemaType } from './types/JSONSchema'; | ||
import type { AST } from './types/AST'; | ||
import type { NormalizedJSONSchema, SchemaType } from './types/JSONSchema'; | ||
export type Processed = Map<NormalizedJSONSchema, Map<SchemaType, AST>>; | ||
export type UsedNames = Set<string>; | ||
export declare function parse(schema: NormalizedJSONSchema | JSONSchema4Type, options: Options, keyName?: string, processed?: Processed, usedNames?: Set<string>): AST; |
@@ -6,3 +6,3 @@ "use strict"; | ||
const util_1 = require("util"); | ||
const typesOfSchema_1 = require("./typesOfSchema"); | ||
const applySchemaTyping_1 = require("./applySchemaTyping"); | ||
const AST_1 = require("./types/AST"); | ||
@@ -18,25 +18,19 @@ const JSONSchema_1 = require("./types/JSONSchema"); | ||
} | ||
const types = (0, typesOfSchema_1.typesOfSchema)(schema); | ||
if (types.length === 1) { | ||
const ast = parseAsTypeWithCache(schema, types[0], options, keyName, processed, usedNames); | ||
(0, utils_1.log)('blue', 'parser', 'Types:', types, 'Input:', schema, 'Output:', ast); | ||
const intersection = schema[JSONSchema_1.Intersection]; | ||
const types = schema[JSONSchema_1.Types]; | ||
if (intersection) { | ||
const ast = parseAsTypeWithCache(intersection, 'ALL_OF', options, keyName, processed, usedNames); | ||
types.forEach(type => { | ||
ast.params.push(parseAsTypeWithCache(schema, type, options, keyName, processed, usedNames)); | ||
}); | ||
(0, utils_1.log)('blue', 'parser', 'Types:', [...types], 'Input:', schema, 'Output:', ast); | ||
return ast; | ||
} | ||
// Be careful to first process the intersection before processing its params, | ||
// so that it gets first pick for standalone name. | ||
const ast = parseAsTypeWithCache({ | ||
[JSONSchema_1.Parent]: schema[JSONSchema_1.Parent], | ||
$id: schema.$id, | ||
additionalProperties: schema.additionalProperties, | ||
allOf: [], | ||
description: schema.description, | ||
required: schema.required, | ||
title: schema.title, | ||
}, 'ALL_OF', options, keyName, processed, usedNames); | ||
ast.params = types.map(type => | ||
// We hoist description (for comment) and id/title (for standaloneName) | ||
// to the parent intersection type, so we remove it from the children. | ||
parseAsTypeWithCache((0, utils_1.maybeStripNameHints)(schema), type, options, keyName, processed, usedNames)); | ||
(0, utils_1.log)('blue', 'parser', 'Types:', types, 'Input:', schema, 'Output:', ast); | ||
return ast; | ||
if (types.size === 1) { | ||
const type = [...types][0]; | ||
const ast = parseAsTypeWithCache(schema, type, options, keyName, processed, usedNames); | ||
(0, utils_1.log)('blue', 'parser', 'Type:', type, 'Input:', schema, 'Output:', ast); | ||
return ast; | ||
} | ||
throw new ReferenceError('Expected intersection schema. Please file an issue on GitHub.'); | ||
} | ||
@@ -229,4 +223,6 @@ exports.parse = parse; | ||
params: schema.type.map(type => { | ||
const member = Object.assign(Object.assign({}, (0, lodash_1.omit)(schema, '$id', 'description', 'title')), { type, additionalProperties: schema.additionalProperties, required: schema.required }); | ||
return parse((0, utils_1.maybeStripDefault)(member), options, undefined, processed, usedNames); | ||
const member = Object.assign(Object.assign({}, (0, lodash_1.omit)(schema, '$id', 'description', 'title')), { type }); | ||
(0, utils_1.maybeStripDefault)(member); | ||
(0, applySchemaTyping_1.applySchemaTyping)(member); | ||
return parse(member, options, undefined, processed, usedNames); | ||
}), | ||
@@ -233,0 +229,0 @@ type: 'UNION', |
@@ -47,2 +47,4 @@ /// <reference types="lodash" /> | ||
} | ||
export declare const Types: unique symbol; | ||
export declare const Intersection: unique symbol; | ||
/** | ||
@@ -54,3 +56,5 @@ * Normalized JSON schema. | ||
export interface NormalizedJSONSchema extends Omit<LinkedJSONSchema, 'definitions' | 'id'> { | ||
[Intersection]?: NormalizedJSONSchema; | ||
[Parent]: NormalizedJSONSchema | null; | ||
[Types]: ReadonlySet<SchemaType>; | ||
additionalItems?: boolean | NormalizedJSONSchema; | ||
@@ -57,0 +61,0 @@ additionalProperties: boolean | NormalizedJSONSchema; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.isCompound = exports.isPrimitive = exports.isBoolean = exports.getRootSchema = exports.Parent = void 0; | ||
exports.isCompound = exports.isPrimitive = exports.isBoolean = exports.getRootSchema = exports.Intersection = exports.Types = exports.Parent = void 0; | ||
const lodash_1 = require("lodash"); | ||
exports.Parent = Symbol('Parent'); | ||
exports.Types = Symbol('Types'); | ||
exports.Intersection = Symbol('Intersection'); | ||
exports.getRootSchema = (0, lodash_1.memoize)((schema) => { | ||
@@ -7,0 +9,0 @@ const parent = schema[exports.Parent]; |
@@ -10,2 +10,2 @@ import { JSONSchema, SchemaType } from './types/JSONSchema'; | ||
*/ | ||
export declare function typesOfSchema(schema: JSONSchema): readonly [SchemaType, ...SchemaType[]]; | ||
export declare function typesOfSchema(schema: JSONSchema): Set<SchemaType>; |
@@ -17,14 +17,14 @@ "use strict"; | ||
if (schema.tsType) { | ||
return ['CUSTOM_TYPE']; | ||
return new Set(['CUSTOM_TYPE']); | ||
} | ||
// Collect matched types | ||
const matchedTypes = []; | ||
const matchedTypes = new Set(); | ||
for (const [schemaType, f] of Object.entries(matchers)) { | ||
if (f(schema)) { | ||
matchedTypes.push(schemaType); | ||
matchedTypes.add(schemaType); | ||
} | ||
} | ||
// Default to an unnamed schema | ||
if (!matchedTypes.length) { | ||
return ['UNNAMED_SCHEMA']; | ||
if (!matchedTypes.size) { | ||
matchedTypes.add('UNNAMED_SCHEMA'); | ||
} | ||
@@ -31,0 +31,0 @@ return matchedTypes; |
@@ -1,2 +0,2 @@ | ||
import { JSONSchema, LinkedJSONSchema, NormalizedJSONSchema } from './types/JSONSchema'; | ||
import { JSONSchema, LinkedJSONSchema } from './types/JSONSchema'; | ||
import { JSONSchema4 } from 'json-schema'; | ||
@@ -34,13 +34,5 @@ export declare function Try<T>(fn: () => T, err: (e: Error) => any): T; | ||
export declare function maybeStripDefault(schema: LinkedJSONSchema): LinkedJSONSchema; | ||
/** | ||
* Removes the schema's `$id`, `name`, and `description` properties | ||
* if they exist. | ||
* Useful when parsing intersections. | ||
* | ||
* Mutates `schema`. | ||
*/ | ||
export declare function maybeStripNameHints(schema: NormalizedJSONSchema): NormalizedJSONSchema; | ||
export declare function appendToDescription(existingDescription: string | undefined, ...values: string[]): string; | ||
export declare function isSchemaLike(schema: LinkedJSONSchema): boolean; | ||
export declare function isSchemaLike(schema: any): schema is LinkedJSONSchema; | ||
export declare function parseFileAsJSONSchema(filename: string | null, contents: string): JSONSchema4; | ||
export {}; |
@@ -6,3 +6,3 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.parseFileAsJSONSchema = exports.isSchemaLike = exports.appendToDescription = exports.maybeStripNameHints = exports.maybeStripDefault = exports.pathTransform = exports.escapeBlockComment = exports.log = exports.error = exports.generateName = exports.toSafeString = exports.stripExtension = exports.justName = exports.traverse = exports.Try = void 0; | ||
exports.parseFileAsJSONSchema = exports.isSchemaLike = exports.appendToDescription = exports.maybeStripDefault = exports.pathTransform = exports.escapeBlockComment = exports.log = exports.error = exports.generateName = exports.toSafeString = exports.stripExtension = exports.justName = exports.traverse = exports.Try = void 0; | ||
const lodash_1 = require("lodash"); | ||
@@ -69,2 +69,15 @@ const path_1 = require("path"); | ||
} | ||
function traverseIntersection(schema, callback, processed) { | ||
if (typeof schema !== 'object' || !schema) { | ||
return; | ||
} | ||
const r = schema; | ||
const intersection = r[JSONSchema_1.Intersection]; | ||
if (!intersection) { | ||
return; | ||
} | ||
if (Array.isArray(intersection.allOf)) { | ||
traverseArray(intersection.allOf, callback, processed); | ||
} | ||
} | ||
function traverse(schema, callback, processed = new Set(), key) { | ||
@@ -124,2 +137,3 @@ // Handle recursive schemas | ||
} | ||
traverseIntersection(schema, callback, processed); | ||
// technically you can put definitions on any key | ||
@@ -318,22 +332,2 @@ Object.keys(schema) | ||
exports.maybeStripDefault = maybeStripDefault; | ||
/** | ||
* Removes the schema's `$id`, `name`, and `description` properties | ||
* if they exist. | ||
* Useful when parsing intersections. | ||
* | ||
* Mutates `schema`. | ||
*/ | ||
function maybeStripNameHints(schema) { | ||
if ('$id' in schema) { | ||
delete schema.$id; | ||
} | ||
if ('description' in schema) { | ||
delete schema.description; | ||
} | ||
if ('name' in schema) { | ||
delete schema.name; | ||
} | ||
return schema; | ||
} | ||
exports.maybeStripNameHints = maybeStripNameHints; | ||
function appendToDescription(existingDescription, ...values) { | ||
@@ -350,2 +344,3 @@ if (existingDescription) { | ||
} | ||
// top-level schema | ||
const parent = schema[JSONSchema_1.Parent]; | ||
@@ -352,0 +347,0 @@ if (parent === null) { |
{ | ||
"name": "json-schema-to-typescript", | ||
"version": "14.1.0", | ||
"version": "15.0.0", | ||
"description": "compile json schema to typescript typings", | ||
@@ -5,0 +5,0 @@ "main": "dist/src/index.js", |
import {JSONSchemaTypeName, LinkedJSONSchema, NormalizedJSONSchema, Parent} from './types/JSONSchema' | ||
import {appendToDescription, escapeBlockComment, isSchemaLike, justName, toSafeString, traverse} from './utils' | ||
import {Options} from './' | ||
import {applySchemaTyping} from './applySchemaTyping' | ||
import {DereferencedPaths} from './resolver' | ||
@@ -234,2 +235,18 @@ import {isDeepStrictEqual} from 'util' | ||
// Precalculation of the schema types is necessary because the ALL_OF type | ||
// is implemented in a way that mutates the schema object. Detection of the | ||
// NAMED_SCHEMA type relies on the presence of the $id property, which is | ||
// hoisted to a parent schema object during the ALL_OF type implementation, | ||
// and becomes unavailable if the same schema is used in multiple places. | ||
// | ||
// Precalculation of the `ALL_OF` intersection schema is necessary because | ||
// the intersection schema needs to participate in the schema cache during | ||
// the parsing step, so it cannot be re-calculated every time the schema | ||
// is encountered. | ||
rules.set('Pre-calculate schema types and intersections', schema => { | ||
if (schema !== null && typeof schema === 'object') { | ||
applySchemaTyping(schema) | ||
} | ||
}) | ||
export function normalize( | ||
@@ -236,0 +253,0 @@ rootSchema: LinkedJSONSchema, |
@@ -5,27 +5,15 @@ import {JSONSchema4Type, JSONSchema4TypeName} from 'json-schema' | ||
import {Options} from './' | ||
import {typesOfSchema} from './typesOfSchema' | ||
import { | ||
AST, | ||
T_ANY, | ||
T_ANY_ADDITIONAL_PROPERTIES, | ||
TInterface, | ||
TInterfaceParam, | ||
TNamedInterface, | ||
TTuple, | ||
T_UNKNOWN, | ||
T_UNKNOWN_ADDITIONAL_PROPERTIES, | ||
TIntersection, | ||
} from './types/AST' | ||
import { | ||
import {applySchemaTyping} from './applySchemaTyping' | ||
import type {AST, TInterface, TInterfaceParam, TIntersection, TNamedInterface, TTuple} from './types/AST' | ||
import {T_ANY, T_ANY_ADDITIONAL_PROPERTIES, T_UNKNOWN, T_UNKNOWN_ADDITIONAL_PROPERTIES} from './types/AST' | ||
import type { | ||
EnumJSONSchema, | ||
getRootSchema, | ||
isBoolean, | ||
isPrimitive, | ||
JSONSchemaWithDefinitions, | ||
LinkedJSONSchema, | ||
NormalizedJSONSchema, | ||
Parent, | ||
SchemaSchema, | ||
SchemaType, | ||
} from './types/JSONSchema' | ||
import {generateName, log, maybeStripDefault, maybeStripNameHints} from './utils' | ||
import {Intersection, Types, getRootSchema, isBoolean, isPrimitive} from './types/JSONSchema' | ||
import {generateName, log, maybeStripDefault} from './utils' | ||
@@ -51,36 +39,24 @@ export type Processed = Map<NormalizedJSONSchema, Map<SchemaType, AST>> | ||
const types = typesOfSchema(schema) | ||
if (types.length === 1) { | ||
const ast = parseAsTypeWithCache(schema, types[0], options, keyName, processed, usedNames) | ||
log('blue', 'parser', 'Types:', types, 'Input:', schema, 'Output:', ast) | ||
const intersection = schema[Intersection] | ||
const types = schema[Types] | ||
if (intersection) { | ||
const ast = parseAsTypeWithCache(intersection, 'ALL_OF', options, keyName, processed, usedNames) as TIntersection | ||
types.forEach(type => { | ||
ast.params.push(parseAsTypeWithCache(schema, type, options, keyName, processed, usedNames)) | ||
}) | ||
log('blue', 'parser', 'Types:', [...types], 'Input:', schema, 'Output:', ast) | ||
return ast | ||
} | ||
// Be careful to first process the intersection before processing its params, | ||
// so that it gets first pick for standalone name. | ||
const ast = parseAsTypeWithCache( | ||
{ | ||
[Parent]: schema[Parent], | ||
$id: schema.$id, | ||
additionalProperties: schema.additionalProperties, | ||
allOf: [], | ||
description: schema.description, | ||
required: schema.required, | ||
title: schema.title, | ||
}, | ||
'ALL_OF', | ||
options, | ||
keyName, | ||
processed, | ||
usedNames, | ||
) as TIntersection | ||
if (types.size === 1) { | ||
const type = [...types][0] | ||
const ast = parseAsTypeWithCache(schema, type, options, keyName, processed, usedNames) | ||
log('blue', 'parser', 'Type:', type, 'Input:', schema, 'Output:', ast) | ||
return ast | ||
} | ||
ast.params = types.map(type => | ||
// We hoist description (for comment) and id/title (for standaloneName) | ||
// to the parent intersection type, so we remove it from the children. | ||
parseAsTypeWithCache(maybeStripNameHints(schema), type, options, keyName, processed, usedNames), | ||
) | ||
log('blue', 'parser', 'Types:', types, 'Input:', schema, 'Output:', ast) | ||
return ast | ||
throw new ReferenceError('Expected intersection schema. Please file an issue on GitHub.') | ||
} | ||
@@ -298,9 +274,6 @@ | ||
params: (schema.type as JSONSchema4TypeName[]).map(type => { | ||
const member: NormalizedJSONSchema = { | ||
...omit(schema, '$id', 'description', 'title'), | ||
type, | ||
additionalProperties: schema.additionalProperties, | ||
required: schema.required, | ||
} | ||
return parse(maybeStripDefault(member as any), options, undefined, processed, usedNames) | ||
const member: LinkedJSONSchema = {...omit(schema, '$id', 'description', 'title'), type} | ||
maybeStripDefault(member) | ||
applySchemaTyping(member) | ||
return parse(member, options, undefined, processed, usedNames) | ||
}), | ||
@@ -307,0 +280,0 @@ type: 'UNION', |
@@ -73,2 +73,5 @@ import {JSONSchema4, JSONSchema4Type, JSONSchema4TypeName} from 'json-schema' | ||
export const Types = Symbol('Types') | ||
export const Intersection = Symbol('Intersection') | ||
/** | ||
@@ -80,3 +83,5 @@ * Normalized JSON schema. | ||
export interface NormalizedJSONSchema extends Omit<LinkedJSONSchema, 'definitions' | 'id'> { | ||
[Intersection]?: NormalizedJSONSchema | ||
[Parent]: NormalizedJSONSchema | null | ||
[Types]: ReadonlySet<SchemaType> | ||
@@ -83,0 +88,0 @@ additionalItems?: boolean | NormalizedJSONSchema |
@@ -12,13 +12,13 @@ import {isPlainObject} from 'lodash' | ||
*/ | ||
export function typesOfSchema(schema: JSONSchema): readonly [SchemaType, ...SchemaType[]] { | ||
export function typesOfSchema(schema: JSONSchema): Set<SchemaType> { | ||
// tsType is an escape hatch that supercedes all other directives | ||
if (schema.tsType) { | ||
return ['CUSTOM_TYPE'] | ||
return new Set(['CUSTOM_TYPE']) | ||
} | ||
// Collect matched types | ||
const matchedTypes: SchemaType[] = [] | ||
const matchedTypes = new Set<SchemaType>() | ||
for (const [schemaType, f] of Object.entries(matchers)) { | ||
if (f(schema)) { | ||
matchedTypes.push(schemaType as SchemaType) | ||
matchedTypes.add(schemaType as SchemaType) | ||
} | ||
@@ -28,7 +28,7 @@ } | ||
// Default to an unnamed schema | ||
if (!matchedTypes.length) { | ||
return ['UNNAMED_SCHEMA'] | ||
if (!matchedTypes.size) { | ||
matchedTypes.add('UNNAMED_SCHEMA') | ||
} | ||
return matchedTypes as [SchemaType, ...SchemaType[]] | ||
return matchedTypes | ||
} | ||
@@ -35,0 +35,0 @@ |
import {deburr, isPlainObject, trim, upperFirst} from 'lodash' | ||
import {basename, dirname, extname, normalize, sep, posix} from 'path' | ||
import {JSONSchema, LinkedJSONSchema, NormalizedJSONSchema, Parent} from './types/JSONSchema' | ||
import {Intersection, JSONSchema, LinkedJSONSchema, NormalizedJSONSchema, Parent} from './types/JSONSchema' | ||
import {JSONSchema4} from 'json-schema' | ||
@@ -74,2 +74,22 @@ import yaml from 'js-yaml' | ||
function traverseIntersection( | ||
schema: LinkedJSONSchema, | ||
callback: (schema: LinkedJSONSchema, key: string | null) => void, | ||
processed: Set<LinkedJSONSchema>, | ||
) { | ||
if (typeof schema !== 'object' || !schema) { | ||
return | ||
} | ||
const r = schema as unknown as Record<string | symbol, unknown> | ||
const intersection = r[Intersection] as NormalizedJSONSchema | undefined | ||
if (!intersection) { | ||
return | ||
} | ||
if (Array.isArray(intersection.allOf)) { | ||
traverseArray(intersection.allOf, callback, processed) | ||
} | ||
} | ||
export function traverse( | ||
@@ -134,2 +154,3 @@ schema: LinkedJSONSchema, | ||
} | ||
traverseIntersection(schema, callback, processed) | ||
@@ -336,22 +357,2 @@ // technically you can put definitions on any key | ||
/** | ||
* Removes the schema's `$id`, `name`, and `description` properties | ||
* if they exist. | ||
* Useful when parsing intersections. | ||
* | ||
* Mutates `schema`. | ||
*/ | ||
export function maybeStripNameHints(schema: NormalizedJSONSchema): NormalizedJSONSchema { | ||
if ('$id' in schema) { | ||
delete schema.$id | ||
} | ||
if ('description' in schema) { | ||
delete schema.description | ||
} | ||
if ('name' in schema) { | ||
delete schema.name | ||
} | ||
return schema | ||
} | ||
export function appendToDescription(existingDescription: string | undefined, ...values: string[]): string { | ||
@@ -364,6 +365,8 @@ if (existingDescription) { | ||
export function isSchemaLike(schema: LinkedJSONSchema) { | ||
export function isSchemaLike(schema: any): schema is LinkedJSONSchema { | ||
if (!isPlainObject(schema)) { | ||
return false | ||
} | ||
// top-level schema | ||
const parent = schema[Parent] | ||
@@ -370,0 +373,0 @@ if (parent === null) { |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
201372
57
4974