json-schema-to-typescript
Advanced tools
Comparing version 0.1.1 to 1.0.0
@@ -25,23 +25,16 @@ "use strict"; | ||
class Compiler { | ||
constructor(schema) { | ||
constructor(schema, settings) { | ||
this.schema = schema; | ||
this.state = { | ||
interfaces: [], | ||
anonymousSchemaNameGenerator: this.generateSchemaName() | ||
}; | ||
this.id = schema.id || schema.title || "Interface1"; | ||
this.declarations = new Map(); | ||
this.settings = Object.assign({}, Compiler.DEFAULT_SETTINGS, settings); | ||
let decl = this.declareType(this.toTsType(this.schema), this.id, this.id); | ||
} | ||
toString() { | ||
return pretty_printer_1.format(this.state.interfaces | ||
.concat(this.toTsInterface(this.schema)) | ||
.map(_ => _.toString()) | ||
return pretty_printer_1.format(Array.from(this.declarations.values()) | ||
.map(_ => _.toDeclaration(this.settings)) | ||
.join('\n')); | ||
} | ||
*generateSchemaName() { | ||
let counter = 0; | ||
while (++counter) { | ||
yield `Interface${counter}`; | ||
} | ||
} | ||
isRequired(propertyName, schema) { | ||
return schema.required.indexOf(propertyName) > -1; | ||
return schema.required ? schema.required.indexOf(propertyName) > -1 : false; | ||
} | ||
@@ -51,8 +44,7 @@ supportsAdditionalProperties(schema) { | ||
} | ||
toInterfaceName(a) { | ||
return lodash_1.upperFirst(lodash_1.camelCase(a)) | ||
|| this.state.anonymousSchemaNameGenerator.next().value; | ||
toDeclarationName(a) { | ||
return lodash_1.upperFirst(lodash_1.camelCase(a)); | ||
} | ||
getRuleType(rule) { | ||
if (rule.type === 'array' && rule.items && rule.items.type) { | ||
if (rule.type === 'array' && rule.items) { | ||
return RuleType.TypedArray; | ||
@@ -99,44 +91,50 @@ } | ||
// eg. "#/definitions/diskDevice" => ["definitions", "diskDevice"] | ||
parsePath(path) { | ||
return (path.slice(0, 2) === '#/' ? path.slice(2) : path).split('/'); | ||
} | ||
getReference(path, root) { | ||
switch (path.length) { | ||
case 0: throw ReferenceError(`$ref "#{path.join('/')}" points at invalid reference`); | ||
case 1: return root[path[0]]; | ||
default: return this.getReference(path.slice(1), root[path[0]]); | ||
resolveType(path) { | ||
if (path[0] !== '#') | ||
throw new Error("reference must start with #"); | ||
if (path === '#' || path === '#/') | ||
return TsType.Interface.reference(this.id); | ||
const parts = path.slice(2).split('/'); | ||
let ret = this.settings.declareReferenced ? this.declarations.get(parts.join('/')) : undefined; | ||
if (!ret) { | ||
let cur = this.schema; | ||
let i = 0; | ||
for (let i = 0; cur && i < parts.length; ++i) { | ||
cur = cur[parts[i]]; | ||
} | ||
ret = this.toTsType(cur); | ||
if (this.settings.declareReferenced && (this.settings.declareSimpleType || !ret.isSimpleType())) | ||
this.declareType(ret, parts.join('/'), this.settings.useFullReferencePathAsName ? parts.join('/') : lodash_1.last(parts)); | ||
} | ||
return ret; | ||
} | ||
getInterface(name) { | ||
return this.state.interfaces.find(_ => _.name === this.toInterfaceName(name)); | ||
declareType(type, path, id) { | ||
type.id = id; | ||
this.declarations.set(path, type); | ||
return type; | ||
} | ||
createInterfaceNx(rule, name) { | ||
return this.getInterface(name) || (() => { | ||
const a = this.toTsInterface(rule, name); | ||
this.state.interfaces.push(a); | ||
return a; | ||
})(); | ||
} | ||
toStringLiteral(a) { | ||
switch (typeof a) { | ||
case 'boolean': return 'boolean'; // ts doesn't support literal boolean types | ||
case 'number': return 'number'; // ts doesn't support literal numeric types | ||
case 'string': return `"${a}"`; | ||
default: return a; | ||
case 'boolean': return new TsType.Boolean; // ts doesn't support literal boolean types | ||
case 'number': return new TsType.Number; // ts doesn't support literal numeric types | ||
case 'string': return new TsType.Literal(JSON.stringify(a)); | ||
default: return new TsType.Interface(lodash_1.map(a, (v, k) => { | ||
return { | ||
type: this.toStringLiteral(v), | ||
name: k, | ||
required: true | ||
}; | ||
})); | ||
} | ||
} | ||
toTsType(rule, root, name) { | ||
createTsType(rule) { | ||
switch (this.getRuleType(rule)) { | ||
case RuleType.AnonymousSchema: | ||
return new TsType.AnonymousInterface(this.schemaPropsToInterfaceProps(lodash_1.merge({}, Compiler.DEFAULT_SCHEMA, { | ||
required: Object.keys(rule), | ||
properties: rule | ||
}))); | ||
case RuleType.NamedSchema: | ||
return new TsType.NamedClass(this.createInterfaceNx(rule, name).name); | ||
return this.toTsDeclaration(rule); | ||
case RuleType.Enum: | ||
return new TsType.Union(lodash_1.uniqBy(rule.enum.map(_ => this.toTsType(this.toStringLiteral(_), root)), _ => _.toString())); | ||
return new TsType.Union(lodash_1.uniqBy(rule.enum.map(_ => this.toStringLiteral(_)), _ => _.toType(this.settings))); | ||
case RuleType.Any: return new TsType.Any; | ||
case RuleType.Literal: return new TsType.Literal(rule); | ||
case RuleType.TypedArray: return new TsType.Array(this.toTsType(rule.items, root)); | ||
case RuleType.TypedArray: return new TsType.Array(this.toTsType(rule.items)); | ||
case RuleType.Array: return new TsType.Array; | ||
@@ -149,46 +147,45 @@ case RuleType.Boolean: return new TsType.Boolean; | ||
case RuleType.AllOf: | ||
return new TsType.Intersection(rule.allOf.map(_ => { | ||
const path = this.parsePath(_.$ref); | ||
return this.toTsType(_, root, lodash_1.last(path)); | ||
})); | ||
return new TsType.Intersection(rule.allOf.map(_ => this.toTsType(_))); | ||
case RuleType.AnyOf: | ||
return new TsType.Union(rule.anyOf.map(_ => { | ||
const path = this.parsePath(_.$ref); | ||
return this.toTsType(_, root, lodash_1.last(path)); | ||
})); | ||
return new TsType.Union(rule.anyOf.map(_ => this.toTsType(_))); | ||
case RuleType.Reference: | ||
const path = this.parsePath(rule.$ref); | ||
const int = this.getInterface(lodash_1.last(path)); | ||
return int | ||
? new TsType.Literal(int.name) | ||
: this.toTsType(this.getReference(path, root), root, lodash_1.last(path)); | ||
return this.resolveType(rule.$ref); | ||
} | ||
throw "bug"; | ||
} | ||
schemaPropsToInterfaceProps(schema) { | ||
return lodash_1.map(schema.properties, (v, k) => new TsType.InterfaceProperty({ | ||
isRequired: this.isRequired(k, schema), | ||
key: k, | ||
value: this.toTsType(v, schema), | ||
description: v.description | ||
})); | ||
toTsType(rule) { | ||
let type = this.createTsType(rule); | ||
type.id = type.id || rule.id || rule.title; | ||
type.description = type.description || rule.description; | ||
return type; | ||
} | ||
toTsInterface(schema, title) { | ||
schema = lodash_1.merge({}, Compiler.DEFAULT_SCHEMA, schema); | ||
const props = this.schemaPropsToInterfaceProps(schema); | ||
if (this.supportsAdditionalProperties(schema)) { | ||
props.push(new TsType.InterfaceProperty({ | ||
key: '[k: string]', | ||
isRequired: true, | ||
value: (schema.additionalProperties === true | ||
? new TsType.Any | ||
: this.toTsType(schema.additionalProperties, schema)) | ||
})); | ||
toTsDeclaration(schema) { | ||
let copy = lodash_1.merge({}, Compiler.DEFAULT_SCHEMA, schema); | ||
let set = new Set(); | ||
let props = lodash_1.map(copy.properties, (v, k) => { | ||
return { | ||
type: this.toTsType(v), | ||
name: k, | ||
required: this.isRequired(k, copy) | ||
}; | ||
}); | ||
if (props.length === 0 && !("additionalProperties" in schema)) { | ||
if ("default" in schema) | ||
return new TsType.Void; | ||
} | ||
return new TsType.Interface({ | ||
name: this.toInterfaceName(title || schema.title), | ||
description: schema.description, | ||
props: props | ||
}); | ||
if (this.supportsAdditionalProperties(copy)) { | ||
let short = copy.additionalProperties === true; | ||
if (short && props.length === 0) | ||
return new TsType.Any; | ||
let type = short ? new TsType.Any : this.toTsType(copy.additionalProperties); | ||
props.push({ | ||
type: type, | ||
name: '[k: string]', | ||
required: true | ||
}); | ||
} | ||
return new TsType.Interface(props); | ||
} | ||
} | ||
Compiler.DEFAULT_SETTINGS = TsType.DEFAULT_SETTINGS; | ||
Compiler.DEFAULT_SCHEMA = { | ||
@@ -200,4 +197,4 @@ additionalProperties: true, | ||
}; | ||
function compile(schema) { | ||
return new Compiler(schema).toString(); | ||
function compile(schema, settings) { | ||
return new Compiler(schema, settings).toString(); | ||
} | ||
@@ -204,0 +201,0 @@ exports.compile = compile; |
"use strict"; | ||
const lodash_1 = require('lodash'); | ||
exports.DEFAULT_SETTINGS = { | ||
declareSimpleType: false, | ||
declareReferenced: true, | ||
useFullReferencePathAsName: false, | ||
// declareProperties: false, | ||
useInterfaceDeclaration: false, | ||
endTypeWithSemicolon: true, | ||
endPropertyWithSemicolon: true, | ||
declarationDescription: true, | ||
propertyDescription: true, | ||
}; | ||
class TsType { | ||
safeId() { | ||
return this.id && lodash_1.upperFirst(lodash_1.camelCase(this.id)); | ||
} | ||
toBlockComment(settings) { | ||
return this.description && settings.declarationDescription ? `/** ${this.description} */\n` : ''; | ||
} | ||
_toDeclaration(decl, settings) { | ||
return this.toBlockComment(settings) + decl + (settings.endTypeWithSemicolon ? ";" : ""); | ||
} | ||
isSimpleType() { return true; } | ||
toDeclaration(settings) { | ||
return this._toDeclaration(`type ${this.safeId()} = ${this._type(settings)}`, settings); | ||
} | ||
toSafeType(settings) { | ||
return this.toType(settings); | ||
} | ||
toType(settings) { | ||
return this.safeId() || this._type(settings); | ||
} | ||
toString() { | ||
return this._type(exports.DEFAULT_SETTINGS); | ||
} | ||
} | ||
exports.TsType = TsType; | ||
class Any extends TsType { | ||
toString() { | ||
_type() { | ||
return 'any'; | ||
@@ -11,14 +45,10 @@ } | ||
exports.Any = Any; | ||
class Array extends TsType { | ||
constructor(type) { | ||
super(); | ||
this.type = type; | ||
class String extends TsType { | ||
_type() { | ||
return 'string'; | ||
} | ||
toString() { | ||
return `${this.type ? this.type.toString() : (new Any()).toString()}[]`; | ||
} | ||
} | ||
exports.Array = Array; | ||
exports.String = String; | ||
class Boolean extends TsType { | ||
toString() { | ||
_type() { | ||
return 'boolean'; | ||
@@ -28,22 +58,20 @@ } | ||
exports.Boolean = Boolean; | ||
class NamedClass extends TsType { | ||
constructor(name) { | ||
super(); | ||
this.name = name; | ||
class Number extends TsType { | ||
_type() { | ||
return 'number'; | ||
} | ||
toString() { | ||
return this.name; | ||
} | ||
exports.Number = Number; | ||
class Object extends TsType { | ||
_type() { | ||
return 'Object'; | ||
} | ||
} | ||
exports.NamedClass = NamedClass; | ||
class Intersection extends TsType { | ||
constructor(data) { | ||
super(); | ||
this.data = data; | ||
exports.Object = Object; | ||
class Void extends TsType { | ||
_type() { | ||
return 'void'; | ||
} | ||
toString() { | ||
return this.data.join('&'); | ||
} | ||
} | ||
exports.Intersection = Intersection; | ||
exports.Void = Void; | ||
class Literal extends TsType { | ||
@@ -54,3 +82,3 @@ constructor(value) { | ||
} | ||
toString() { | ||
_type() { | ||
return this.value; | ||
@@ -60,21 +88,13 @@ } | ||
exports.Literal = Literal; | ||
class Number extends TsType { | ||
toString() { | ||
return 'number'; | ||
class Array extends TsType { | ||
constructor(type) { | ||
super(); | ||
this.type = type; | ||
} | ||
} | ||
exports.Number = Number; | ||
class Object extends TsType { | ||
toString() { | ||
return 'Object'; | ||
_type(settings) { | ||
return `${(this.type || new Any()).toSafeType(settings)}[]`; | ||
} | ||
} | ||
exports.Object = Object; | ||
class String extends TsType { | ||
toString() { | ||
return 'string'; | ||
} | ||
} | ||
exports.String = String; | ||
class Union extends TsType { | ||
exports.Array = Array; | ||
class Intersection extends TsType { | ||
constructor(data) { | ||
@@ -84,29 +104,24 @@ super(); | ||
} | ||
toString() { | ||
return this.data.join('|'); | ||
isSimpleType() { return this.data.filter(_ => !(_ instanceof Void)).length <= 1; } | ||
_type(settings) { | ||
return this.data | ||
.filter(_ => !(_ instanceof Void)) | ||
.map(_ => _.toSafeType(settings)) | ||
.join('&'); | ||
} | ||
} | ||
exports.Union = Union; | ||
class Void extends TsType { | ||
toString() { | ||
return 'void'; | ||
toSafeType(settings) { | ||
return `${this.toType(settings)}`; | ||
} | ||
} | ||
exports.Void = Void; | ||
class InterfaceProperty extends TsType { | ||
constructor(data) { | ||
super(); | ||
this.data = data; | ||
exports.Intersection = Intersection; | ||
class Union extends Intersection { | ||
isSimpleType() { return this.data.length <= 1; } | ||
_type(settings) { | ||
return this.data | ||
.map(_ => _.toSafeType(settings)) | ||
.join('|'); | ||
} | ||
toString() { | ||
return [ | ||
this.data.key, | ||
`${this.data.isRequired ? '' : '?'}: `, | ||
`${this.data.value.toString()};`, | ||
this.data.description ? ` // ${this.data.description}` : '' | ||
].join(''); | ||
} | ||
} | ||
exports.InterfaceProperty = InterfaceProperty; | ||
class AnonymousInterface extends TsType { | ||
exports.Union = Union; | ||
class Interface extends TsType { | ||
constructor(props) { | ||
@@ -116,30 +131,32 @@ super(); | ||
} | ||
toString() { | ||
return `{ | ||
${this.props.join('\n')} | ||
}`; | ||
static reference(id) { | ||
let ret = new Interface([]); | ||
ret.id = id; | ||
return ret; | ||
} | ||
} | ||
exports.AnonymousInterface = AnonymousInterface; | ||
class Interface extends TsType { | ||
constructor(data) { | ||
super(); | ||
this.data = data; | ||
_type(settings, declaration = false) { | ||
let id = this.safeId(); | ||
return declaration || !id ? `{ | ||
${this.props.map(_ => { | ||
let decl = _.name; | ||
if (!_.required) | ||
decl += '?'; | ||
decl += ": " + _.type.toType(settings); | ||
if (settings.endPropertyWithSemicolon) | ||
decl += ';'; | ||
if (settings.propertyDescription && _.type.description) | ||
decl += ' // ' + _.type.description; | ||
return decl; | ||
}).join('\n')} | ||
}` : id; | ||
} | ||
get name() { return this.data.name; } | ||
toBlockComment(a) { | ||
return `/* | ||
${a} | ||
*/ | ||
`; | ||
isSimpleType() { return false; } | ||
toDeclaration(settings) { | ||
if (settings.useInterfaceDeclaration) | ||
return `${this.toBlockComment(settings)}interface ${this.safeId()} ${this._type(settings, true)}`; | ||
else | ||
return this._toDeclaration(`type ${this.safeId()} = ${this._type(settings, true)}`, settings); | ||
} | ||
toString() { | ||
return `${this.data.description | ||
? this.toBlockComment(this.data.description) | ||
: ''}interface ${this.data.name} { | ||
${this.data.props.join('\n')} | ||
}`; | ||
} | ||
} | ||
exports.Interface = Interface; | ||
//# sourceMappingURL=TsTypes.js.map |
{ | ||
"name": "json-schema-to-typescript", | ||
"version": "0.1.1", | ||
"version": "1.0.0", | ||
"description": "compile json schema to typescript typings", | ||
"main": "dist/index.js", | ||
"scripts": { | ||
"build": "tsc", | ||
"test": "ava ./test/test.js", | ||
"watch": "watch \"npm run build\" ./src/ ./test/" | ||
"tsc": "tsc", | ||
"build": "gulp tsc", | ||
"test": "gulp test", | ||
"debug": "gulp debug", | ||
"watch": "watch \"npm run test\" ./src/ ./test/" | ||
}, | ||
@@ -34,13 +36,14 @@ "repository": { | ||
"lodash": "^4.6.1", | ||
"typescript": "^1.8.9" | ||
"typescript": "^2.0.0" | ||
}, | ||
"devDependencies": { | ||
"ava": "^0.13.0", | ||
"colors": "^1.1.2", | ||
"diff": "^2.2.2", | ||
"glob-promise": "^1.0.6", | ||
"json-schema-test-suite": "github:json-schema/JSON-Schema-Test-Suite#node", | ||
"typings": "^0.7.9", | ||
"@types/chai": "^3.4.29", | ||
"@types/lodash": "0.0.28", | ||
"@types/mocha": "^2.2.28", | ||
"@types/node": "^4.0.30", | ||
"chai": "^3.5.0", | ||
"gulp": "^3.9.1", | ||
"mocha": "^2.5.3", | ||
"watch": "^0.17.1" | ||
} | ||
} |
206
src/index.ts
@@ -6,7 +6,9 @@ import {camelCase, isPlainObject, last, map, merge, uniqBy, upperFirst} from 'lodash' | ||
import * as TsType from './TsTypes' | ||
process.platform | ||
enum RuleType {"Any","TypedArray","Enum","AllOf","AnyOf","Reference","NamedSchema", "AnonymousSchema", | ||
"String","Number","Void","Object","Array","Boolean","Literal"} | ||
class Compiler { | ||
static DEFAULT_SETTINGS = TsType.DEFAULT_SETTINGS; | ||
@@ -20,9 +22,13 @@ static DEFAULT_SCHEMA: JSONSchema.Schema = { | ||
constructor(private schema: JSONSchema.Schema) {} | ||
constructor(private schema: JSONSchema.Schema, settings?: TsType.TsTypeSettings) { | ||
this.id = schema.id || schema.title || "Interface1"; | ||
this.declarations = new Map(); | ||
this.settings = Object.assign({}, Compiler.DEFAULT_SETTINGS, settings); | ||
let decl = this.declareType(this.toTsType(this.schema), this.id, this.id); | ||
} | ||
toString(): string { | ||
return format( | ||
this.state.interfaces | ||
.concat(this.toTsInterface(this.schema)) | ||
.map(_ => _.toString()) | ||
Array.from(this.declarations.values()) | ||
.map(_ => _.toDeclaration(this.settings)) | ||
.join('\n') | ||
@@ -32,19 +38,8 @@ ) | ||
private state: { | ||
interfaces: TsType.Interface[], | ||
anonymousSchemaNameGenerator: IterableIterator<string> | ||
} = { | ||
interfaces: [], | ||
anonymousSchemaNameGenerator: this.generateSchemaName() | ||
} | ||
private settings: TsType.TsTypeSettings; | ||
private id: string; | ||
private declarations: Map<string, TsType.TsType>; | ||
private *generateSchemaName(): IterableIterator<string> { | ||
let counter = 0 | ||
while (++counter) { | ||
yield `Interface${counter}` | ||
} | ||
} | ||
private isRequired(propertyName: string, schema: JSONSchema.Schema): boolean { | ||
return schema.required.indexOf(propertyName) > -1 | ||
return schema.required ? schema.required.indexOf(propertyName) > -1 : false; | ||
} | ||
@@ -56,9 +51,8 @@ | ||
private toInterfaceName (a: string): string { | ||
return upperFirst(camelCase(a)) | ||
|| this.state.anonymousSchemaNameGenerator.next().value | ||
private toDeclarationName (a: string): string { | ||
return upperFirst(camelCase(a)) | ||
} | ||
private getRuleType (rule: JSONSchema.Schema): RuleType { | ||
if (rule.type === 'array' && rule.items && rule.items.type) { | ||
if (rule.type === 'array' && rule.items) { | ||
return RuleType.TypedArray | ||
@@ -106,32 +100,42 @@ } | ||
// eg. "#/definitions/diskDevice" => ["definitions", "diskDevice"] | ||
private parsePath (path: string): string[] { | ||
return (path.slice(0, 2) === '#/' ? path.slice(2) : path).split('/') | ||
} | ||
private getReference(path: string[], root: JSONSchema.Schema): JSONSchema.Schema { | ||
switch (path.length) { | ||
case 0: throw ReferenceError(`$ref "#{path.join('/')}" points at invalid reference`) | ||
case 1: return root[path[0]] | ||
default: return this.getReference(path.slice(1), root[path[0]]) | ||
private resolveType(path: string): TsType.TsType { | ||
if (path[0] !== '#') | ||
throw new Error("reference must start with #"); | ||
if (path === '#' || path === '#/') | ||
return TsType.Interface.reference(this.id); | ||
const parts = path.slice(2).split('/'); | ||
let ret = this.settings.declareReferenced ? this.declarations.get(parts.join('/')) : undefined; | ||
if (!ret) { | ||
let cur: any = this.schema; | ||
let i = 0; | ||
for (let i = 0; cur && i < parts.length; ++i) { | ||
cur = cur[parts[i]]; | ||
} | ||
ret = this.toTsType(cur); | ||
if (this.settings.declareReferenced && (this.settings.declareSimpleType || !ret.isSimpleType())) | ||
this.declareType(ret, parts.join('/'), this.settings.useFullReferencePathAsName ? parts.join('/') : last(parts)); | ||
} | ||
return ret; | ||
} | ||
private getInterface (name: string): TsType.Interface { | ||
return this.state.interfaces.find(_ => _.name === this.toInterfaceName(name)) | ||
private declareType(type: TsType.TsType, path: string, id: string) { | ||
type.id = id; | ||
this.declarations.set(path, type); | ||
return type; | ||
} | ||
private createInterfaceNx (rule: JSONSchema.Schema, name: string): TsType.Interface { | ||
return this.getInterface(name) || (() => { | ||
const a = this.toTsInterface(rule, name) | ||
this.state.interfaces.push(a) | ||
return a | ||
})() | ||
} | ||
private toStringLiteral(a: boolean|number|string|Object): string|Object { | ||
private toStringLiteral(a: boolean|number|string|Object): TsType.TsType { | ||
switch (typeof a) { | ||
case 'boolean': return 'boolean' // ts doesn't support literal boolean types | ||
case 'number': return 'number' // ts doesn't support literal numeric types | ||
case 'string': return `"${a}"` | ||
default: return a | ||
case 'boolean': return new TsType.Boolean; // ts doesn't support literal boolean types | ||
case 'number': return new TsType.Number; // ts doesn't support literal numeric types | ||
case 'string': return new TsType.Literal(JSON.stringify(a)); | ||
default: return new TsType.Interface(map( | ||
<any>a, | ||
(v: JSONSchema.Schema, k: string) => { | ||
return { | ||
type: this.toStringLiteral(v), | ||
name: k, | ||
required: true | ||
}; | ||
})); | ||
// TODO: support array types? | ||
@@ -143,25 +147,15 @@ // TODO: support enums of enums | ||
private toTsType (rule: JSONSchema.Schema, root: JSONSchema.Schema, name?: string): TsType.TsType { | ||
private createTsType (rule: JSONSchema.Schema): TsType.TsType { | ||
switch (this.getRuleType(rule)) { | ||
case RuleType.AnonymousSchema: | ||
return new TsType.AnonymousInterface( | ||
this.schemaPropsToInterfaceProps(merge({}, Compiler.DEFAULT_SCHEMA, { | ||
required: Object.keys(rule), | ||
properties: rule | ||
})) | ||
) | ||
case RuleType.NamedSchema: | ||
return new TsType.NamedClass( | ||
this.createInterfaceNx(rule, name).name | ||
) | ||
return this.toTsDeclaration(rule); | ||
case RuleType.Enum: | ||
return new TsType.Union(uniqBy( | ||
rule.enum.map(_ => | ||
this.toTsType(this.toStringLiteral(_), root) | ||
) | ||
, _ => _.toString()) | ||
rule.enum!.map(_ => this.toStringLiteral(_)) | ||
, _ => _.toType(this.settings)) | ||
) | ||
case RuleType.Any: return new TsType.Any | ||
case RuleType.Literal: return new TsType.Literal(rule) | ||
case RuleType.TypedArray: return new TsType.Array(this.toTsType(rule.items, root)) | ||
case RuleType.TypedArray: return new TsType.Array(this.toTsType(rule.items!)) | ||
case RuleType.Array: return new TsType.Array | ||
@@ -174,57 +168,49 @@ case RuleType.Boolean: return new TsType.Boolean | ||
case RuleType.AllOf: | ||
return new TsType.Intersection(rule.allOf.map(_ => { | ||
const path = this.parsePath(_.$ref) | ||
return this.toTsType(_, root, last(path)) | ||
})) | ||
return new TsType.Intersection(rule.allOf!.map(_ => this.toTsType(_))) | ||
case RuleType.AnyOf: | ||
return new TsType.Union(rule.anyOf.map(_ => { | ||
const path = this.parsePath(_.$ref) | ||
return this.toTsType(_, root, last(path)) | ||
})) | ||
return new TsType.Union(rule.anyOf!.map(_ => this.toTsType(_))) | ||
case RuleType.Reference: | ||
const path = this.parsePath(rule.$ref) | ||
const int = this.getInterface(last(path)) | ||
return int | ||
? new TsType.Literal(int.name) | ||
: this.toTsType(this.getReference(path, root), root, last(path)) | ||
return this.resolveType(rule.$ref!); | ||
} | ||
throw "bug"; | ||
} | ||
private toTsType (rule: JSONSchema.Schema): TsType.TsType { | ||
let type = this.createTsType(rule); | ||
type.id = type.id || rule.id || rule.title; | ||
type.description = type.description || rule.description; | ||
return type; | ||
} | ||
private schemaPropsToInterfaceProps (schema: JSONSchema.Schema): TsType.InterfaceProperty[] { | ||
return map( | ||
schema.properties, | ||
(v: JSONSchema.Schema, k: string) => new TsType.InterfaceProperty({ | ||
isRequired: this.isRequired(k, schema), | ||
key: k, | ||
value: this.toTsType(v, schema), | ||
description: v.description | ||
}) | ||
) | ||
} | ||
private toTsInterface (schema: JSONSchema.Schema, title?: string): TsType.Interface { | ||
schema = merge({}, Compiler.DEFAULT_SCHEMA, schema) | ||
const props = this.schemaPropsToInterfaceProps(schema) | ||
if (this.supportsAdditionalProperties(schema)) { | ||
props.push(new TsType.InterfaceProperty({ | ||
key: '[k: string]', | ||
isRequired: true, | ||
value: ( | ||
schema.additionalProperties === true | ||
? new TsType.Any | ||
: this.toTsType(<JSONSchema.Schema>schema.additionalProperties, schema) | ||
) | ||
})) | ||
private toTsDeclaration (schema: JSONSchema.Schema) : TsType.TsType { | ||
let copy = merge({}, Compiler.DEFAULT_SCHEMA, schema); | ||
let set = new Set(); | ||
let props = map( | ||
copy.properties!, | ||
(v: JSONSchema.Schema, k: string) => { | ||
return { | ||
type: this.toTsType(v), | ||
name: k, | ||
required: this.isRequired(k, copy) | ||
}; | ||
}); | ||
if (props.length === 0 && !("additionalProperties" in schema)) { | ||
if ("default" in schema) | ||
return new TsType.Void; | ||
} | ||
return new TsType.Interface({ | ||
name: this.toInterfaceName(title || schema.title), | ||
description: schema.description, | ||
props | ||
}) | ||
if (this.supportsAdditionalProperties(copy)) { | ||
let short = copy.additionalProperties === true; | ||
if (short && props.length === 0) | ||
return new TsType.Any; | ||
let type = short ? new TsType.Any : this.toTsType(<JSONSchema.Schema>copy.additionalProperties); | ||
props.push({ | ||
type: type, | ||
name: '[k: string]', | ||
required: true | ||
}); | ||
} | ||
return new TsType.Interface(props); | ||
} | ||
} | ||
export function compile(schema: JSONSchema.Schema): string { | ||
return new Compiler(schema).toString() | ||
export function compile(schema: JSONSchema.Schema, settings?: TsType.TsTypeSettings): string { | ||
return new Compiler(schema, settings).toString() | ||
} | ||
@@ -231,0 +217,0 @@ |
declare module JSONSchema { | ||
interface Schema { | ||
$ref?: string | ||
additionalProperties?: boolean|Schema | ||
type SimpleTypes = "array" | "boolean" | "integer" | "null" | "number" | "object" | "string"; | ||
/** Core schema meta-schema */ | ||
type HttpJsonSchemaOrgDraft04Schema = { | ||
id?: string; | ||
$schema?: string; | ||
title?: string; | ||
description?: string; | ||
default?: any; | ||
multipleOf?: number; | ||
maximum?: number; | ||
exclusiveMaximum?: boolean; | ||
minimum?: number; | ||
exclusiveMinimum?: boolean; | ||
maxLength?: number; | ||
minLength?: number; | ||
pattern?: string; | ||
additionalItems?: boolean | HttpJsonSchemaOrgDraft04Schema; | ||
items?: HttpJsonSchemaOrgDraft04Schema | HttpJsonSchemaOrgDraft04Schema[]; | ||
maxItems?: number; | ||
minItems?: number; | ||
uniqueItems?: boolean; | ||
maxProperties?: number; | ||
minProperties?: number; | ||
required?: string[]; | ||
additionalProperties?: boolean | HttpJsonSchemaOrgDraft04Schema; | ||
definitions?: { | ||
[a: string]: Schema | ||
} | ||
description?: string | ||
enum?: (Schema|Type)[] | ||
items?: Schema | ||
minimum?: number | ||
minItems?: number | ||
maxLength?: number | ||
minLength?: number | ||
allOf?: Schema[] | ||
anyOf?: Schema[] | ||
oneOf?: Schema[] | ||
[k: string]: HttpJsonSchemaOrgDraft04Schema; | ||
}; | ||
properties?: { | ||
[a: string]: Schema | ||
} | ||
required?: string[] | ||
title?: string | ||
type?: Type | ||
uniqueItems?: boolean | ||
} | ||
[k: string]: HttpJsonSchemaOrgDraft04Schema; | ||
}; | ||
patternProperties?: { | ||
[k: string]: HttpJsonSchemaOrgDraft04Schema; | ||
}; | ||
dependencies?: { | ||
[k: string]: HttpJsonSchemaOrgDraft04Schema | string[]; | ||
}; | ||
enum?: any[]; | ||
type?: SimpleTypes | SimpleTypes[]; | ||
allOf?: HttpJsonSchemaOrgDraft04Schema[]; | ||
anyOf?: HttpJsonSchemaOrgDraft04Schema[]; | ||
oneOf?: HttpJsonSchemaOrgDraft04Schema[]; | ||
not?: HttpJsonSchemaOrgDraft04Schema; | ||
[k: string]: any; | ||
}; | ||
type Type = "array"|"boolean"|"integer"|"null"|"number"|"object"|"string" | ||
type Schema = HttpJsonSchemaOrgDraft04Schema & { $ref?: string }; | ||
} |
@@ -0,40 +1,82 @@ | ||
import {camelCase, upperFirst} from 'lodash' | ||
export type TsTypeSettings = { | ||
declareSimpleType?: boolean; | ||
declareReferenced?: boolean; | ||
useFullReferencePathAsName?: boolean; | ||
// TODO declareProperties?: boolean; | ||
useInterfaceDeclaration?: boolean; | ||
endTypeWithSemicolon?: boolean; | ||
endPropertyWithSemicolon?: boolean; | ||
declarationDescription?: boolean; | ||
propertyDescription?: boolean; | ||
}; | ||
export var DEFAULT_SETTINGS: TsTypeSettings = { | ||
declareSimpleType: false, | ||
declareReferenced: true, | ||
useFullReferencePathAsName: false, | ||
// declareProperties: false, | ||
useInterfaceDeclaration: false, | ||
endTypeWithSemicolon: true, | ||
endPropertyWithSemicolon: true, | ||
declarationDescription: true, | ||
propertyDescription: true, | ||
}; | ||
export abstract class TsType { | ||
abstract toString(): string | ||
id?: string; | ||
description?: string; | ||
protected safeId() { | ||
return this.id && upperFirst(camelCase(this.id)); | ||
} | ||
protected toBlockComment(settings: TsTypeSettings) { | ||
return this.description && settings.declarationDescription ? `/** ${this.description} */\n` : ''; | ||
} | ||
protected _toDeclaration(decl: string, settings: TsTypeSettings): string | ||
{ | ||
return this.toBlockComment(settings) + decl + (settings.endTypeWithSemicolon ? ";" : ""); | ||
} | ||
protected abstract _type(settings: TsTypeSettings): string; | ||
isSimpleType() { return true; } | ||
toDeclaration(settings: TsTypeSettings): string | ||
{ | ||
return this._toDeclaration(`type ${this.safeId()} = ${this._type(settings)}`, settings); | ||
} | ||
toSafeType(settings: TsTypeSettings): string | ||
{ | ||
return this.toType(settings); | ||
} | ||
toType(settings: TsTypeSettings): string | ||
{ | ||
return this.safeId() || this._type(settings); | ||
} | ||
toString() : string { | ||
return this._type(DEFAULT_SETTINGS); | ||
} | ||
} | ||
export type TsProp = { | ||
name: string; | ||
required: boolean; | ||
type: TsType; | ||
} | ||
export class Any extends TsType { | ||
toString() { | ||
_type() { | ||
return 'any' | ||
} | ||
} | ||
export class Array extends TsType { | ||
constructor(private type?: TsType) { super() } | ||
toString() { | ||
return `${this.type ? this.type.toString() : (new Any()).toString()}[]` | ||
export class String extends TsType { | ||
_type() { | ||
return 'string' | ||
} | ||
} | ||
export class Boolean extends TsType { | ||
toString() { | ||
_type() { | ||
return 'boolean' | ||
} | ||
} | ||
export class NamedClass extends TsType { | ||
constructor(private name: string) { super() } | ||
toString() { | ||
return this.name | ||
} | ||
} | ||
export class Intersection extends TsType { | ||
constructor(private data: TsType[]) { super() } | ||
toString() { | ||
return this.data.join('&') | ||
} | ||
} | ||
export class Literal extends TsType { | ||
constructor(private value: any) { super() } | ||
toString() { | ||
return this.value | ||
} | ||
} | ||
export class Number extends TsType { | ||
toString() { | ||
_type() { | ||
return 'number' | ||
@@ -44,71 +86,80 @@ } | ||
export class Object extends TsType { | ||
toString() { | ||
_type() { | ||
return 'Object' | ||
} | ||
} | ||
export class String extends TsType { | ||
toString() { | ||
return 'string' | ||
} | ||
} | ||
export class Union extends TsType { | ||
constructor(private data: TsType[]) { super() } | ||
toString() { | ||
return this.data.join('|') | ||
} | ||
} | ||
export class Void extends TsType { | ||
toString() { | ||
_type() { | ||
return 'void' | ||
} | ||
} | ||
export class InterfaceProperty extends TsType { | ||
constructor(private data: { | ||
isRequired: boolean, | ||
key: string, | ||
value: TsType, | ||
description?: string | ||
}) { super() } | ||
toString(): string { | ||
return [ | ||
this.data.key, | ||
`${this.data.isRequired ? '' : '?'}: `, | ||
`${this.data.value.toString()};`, | ||
this.data.description ? ` // ${this.data.description}` : '' | ||
].join('') | ||
export class Literal extends TsType { | ||
constructor(private value: any) { super() } | ||
_type() { | ||
return this.value | ||
} | ||
} | ||
export class AnonymousInterface extends TsType { | ||
constructor(private props: InterfaceProperty[]) { | ||
super() | ||
export class Array extends TsType { | ||
constructor(private type?: TsType) { super() } | ||
_type(settings: TsTypeSettings) { | ||
return `${(this.type || new Any()).toSafeType(settings)}[]` | ||
} | ||
toString() { | ||
return `{ | ||
${this.props.join('\n')} | ||
}` | ||
} | ||
export class Intersection extends TsType { | ||
constructor(protected data: TsType[]) { | ||
super() | ||
} | ||
isSimpleType() { return this.data.filter(_ => !(_ instanceof Void)).length <= 1; } | ||
_type(settings: TsTypeSettings) { | ||
return this.data | ||
.filter(_ => !(_ instanceof Void)) | ||
.map(_ => _.toSafeType(settings)) | ||
.join('&') | ||
} | ||
toSafeType(settings: TsTypeSettings) { | ||
return `${this.toType(settings)}`; | ||
} | ||
} | ||
export class Union extends Intersection { | ||
isSimpleType() { return this.data.length <= 1; } | ||
_type(settings: TsTypeSettings) { | ||
return this.data | ||
.map(_ => _.toSafeType(settings)) | ||
.join('|') | ||
} | ||
} | ||
export class Interface extends TsType { | ||
constructor(private data: {name: string, description?: string, props: InterfaceProperty[]}) { | ||
constructor(private props: TsProp[]) { | ||
super() | ||
} | ||
get name (){ return this.data.name } | ||
private toBlockComment(a: string) { | ||
return `/* | ||
${a} | ||
*/ | ||
` | ||
static reference(id: string) { | ||
let ret = new Interface([]); | ||
ret.id = id; | ||
return ret; | ||
} | ||
toString(): string { | ||
return `${ | ||
this.data.description | ||
? this.toBlockComment(this.data.description) | ||
: '' | ||
}interface ${this.data.name} { | ||
${this.data.props.join('\n')} | ||
}` | ||
protected _type(settings: TsTypeSettings, declaration: boolean = false) { | ||
let id = this.safeId(); | ||
return declaration || !id ? `{ | ||
${this.props.map(_ => { | ||
let decl = _.name; | ||
if (!_.required) | ||
decl += '?'; | ||
decl += ": " + _.type.toType(settings); | ||
if (settings.endPropertyWithSemicolon) | ||
decl += ';'; | ||
if (settings.propertyDescription && _.type.description) | ||
decl += ' // ' + _.type.description; | ||
return decl; | ||
}).join('\n')} | ||
}` : id; | ||
} | ||
} | ||
isSimpleType() { return false; } | ||
toDeclaration(settings: TsTypeSettings): string { | ||
if (settings.useInterfaceDeclaration) | ||
return `${this.toBlockComment(settings)}interface ${this.safeId()} ${this._type(settings, true)}` | ||
else | ||
return this._toDeclaration(`type ${this.safeId()} = ${this._type(settings, true)}`, settings); | ||
} | ||
} |
@@ -7,11 +7,19 @@ { | ||
"preserveConstEnums": true, | ||
"outDir": "dist", | ||
"outDir": "out", | ||
"sourceMap": true, | ||
"target": "es6" | ||
"target": "es6", | ||
"strictNullChecks": true, | ||
"types": [ | ||
"node", | ||
"mocha" | ||
] | ||
}, | ||
"files": [ | ||
"./typings/main.d.ts", | ||
"./src/JSONSchema.d.ts", | ||
"./src/index.ts" | ||
"src/JSONSchema.d.ts", | ||
"src/index.ts", | ||
"test/test.ts" | ||
], | ||
"include": [ | ||
"test/cases/*.ts" | ||
] | ||
} |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
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
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Shell access
Supply chain riskThis module accesses the system shell. Accessing the system shell increases the risk of executing arbitrary code.
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
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
51565
26
1518
0
1
8
2
+ Addedtypescript@2.9.2(transitive)
- Removedtypescript@1.8.10(transitive)
Updatedtypescript@^2.0.0