From f16fabc700e2953c3b3181d79aacdb839e9333ce Mon Sep 17 00:00:00 2001 From: Jonathan Ogilvie Date: Wed, 24 Aug 2016 16:34:35 -0400 Subject: [PATCH 01/27] Adding support for ts enums; added tsEnumNames to schema --- dist/index.d.ts | 13 ++++++- dist/index.js | 90 +++++++++++++++++++++++++++++++++++++++-------- package.json | 2 +- src/JSONSchema.ts | 4 +++ src/TsTypes.ts | 35 +++++++++++++++++- src/index.ts | 64 ++++++++++++++++++++++++--------- 6 files changed, 173 insertions(+), 35 deletions(-) diff --git a/dist/index.d.ts b/dist/index.d.ts index 31142f52..15f45fd0 100644 --- a/dist/index.d.ts +++ b/dist/index.d.ts @@ -1,6 +1,6 @@ // Generated by dts-bundle v0.5.0 -export function compile(schema: JSONSchema.Schema, settings?: TsType.TsTypeSettings): string; +export function compile(schema: JSONSchema.Schema, path: string, settings?: TsType.TsTypeSettings): string; export function compileFromFile(inputFilename: string): Promise; export namespace JSONSchema { @@ -42,6 +42,7 @@ export namespace JSONSchema { [k: string]: HttpJsonSchemaOrgDraft04Schema | string[]; }; enum?: any[]; + tsEnumNames?: string[]; type?: SimpleTypes | SimpleTypes[]; allOf?: HttpJsonSchemaOrgDraft04Schema[]; anyOf?: HttpJsonSchemaOrgDraft04Schema[]; @@ -60,6 +61,8 @@ export namespace TsType { declareReferenced?: boolean; useFullReferencePathAsName?: boolean; useInterfaceDeclaration?: boolean; + useTypescriptEnums?: boolean; + exportInterfaces?: boolean; endTypeWithSemicolon?: boolean; endPropertyWithSemicolon?: boolean; declarationDescription?: boolean; @@ -121,6 +124,14 @@ export namespace TsType { isSimpleType(): boolean; _type(settings: TsTypeSettings): string; } + class Enum extends Intersection { + protected data: TsType[]; + protected literals?: TsType[]; + constructor(data: TsType[], literals?: TsType[]); + isSimpleType(): boolean; + _type(settings: TsTypeSettings, declaration?: boolean): string; + toDeclaration(settings: TsTypeSettings): string; + } class Interface extends TsType { constructor(props: TsProp[]); static reference(id: string): Interface; diff --git a/dist/index.js b/dist/index.js index 64c28aae..c50779e4 100644 --- a/dist/index.js +++ b/dist/index.js @@ -14,6 +14,8 @@ var TsType; useFullReferencePathAsName: false, // declareProperties: false, useInterfaceDeclaration: false, + useTypescriptEnums: false, + exportInterfaces: false, endTypeWithSemicolon: true, endPropertyWithSemicolon: true, declarationDescription: true, @@ -170,6 +172,38 @@ var TsType; return Union; }(Intersection)); TsType_1.Union = Union; + var Enum = (function (_super) { + __extends(Enum, _super); + function Enum(data, literals) { + _super.call(this, data); + this.data = data; + this.literals = literals; + } + Enum.prototype.isSimpleType = function () { return false; }; + Enum.prototype._type = function (settings, declaration) { + if (declaration === void 0) { declaration = false; } + var id = this.safeId(); + var literals = this.literals; + // TODO if literals exist, get literal[i] below and write it first + return declaration || !id ? "{\n " + this.data.map(function (_, i) { + var literal; + var decl = ''; + if (literals) { + literal = literals[i]; + decl += literal; + decl += '='; + } + decl += _.toType(settings); + decl += ','; + return decl; + }).join('\n') + "\n }" : id; + }; + Enum.prototype.toDeclaration = function (settings) { + return this.toBlockComment(settings) + "enum " + this.safeId() + " " + this._type(settings, true); + }; + return Enum; + }(Intersection)); + TsType_1.Enum = Enum; var Interface = (function (_super) { __extends(Interface, _super); function Interface(props) { @@ -199,7 +233,7 @@ var TsType; Interface.prototype.isSimpleType = function () { return false; }; Interface.prototype.toDeclaration = function (settings) { if (settings.useInterfaceDeclaration) - return this.toBlockComment(settings) + "interface " + this.safeId() + " " + this._type(settings, true); + return "" + this.toBlockComment(settings) + (settings.exportInterfaces ? "export " : "") + "interface " + this.safeId() + " " + this._type(settings, true); else return this._toDeclaration("type " + this.safeId() + " = " + this._type(settings, true), settings); }; @@ -212,6 +246,7 @@ var TsType; "use strict"; var lodash_1 = require('lodash'); var fs_1 = require('fs'); +var path_1 = require('path'); var pretty_printer_1 = require('./pretty-printer'); var TsTypes_1 = require('./TsTypes'); var RuleType; @@ -233,10 +268,12 @@ var RuleType; RuleType[RuleType["Literal"] = 14] = "Literal"; })(RuleType || (RuleType = {})); var Compiler = (function () { - function Compiler(schema, settings) { + function Compiler(schema, filePath, settings) { this.schema = schema; - this.id = schema.id || schema.title || "Interface1"; + var path = path_1.resolve(filePath); + this.filePath = path_1.parse(path); this.declarations = new Map(); + this.id = schema.id || schema.title || this.filePath.name || "Interface1"; this.settings = Object.assign({}, Compiler.DEFAULT_SETTINGS, settings); var decl = this.declareType(this.toTsType(this.schema), this.id, this.id); } @@ -298,12 +335,24 @@ var Compiler = (function () { return /^[\d\.]+$/.test(a); }; // eg. "#/definitions/diskDevice" => ["definitions", "diskDevice"] - Compiler.prototype.resolveType = function (path) { - if (path[0] !== '#') - throw new Error("reference must start with #"); - if (path === '#' || path === '#/') + Compiler.prototype.resolveType = function (refPath) { + if (refPath === '#' || refPath === '#/') { return TsTypes_1.TsType.Interface.reference(this.id); - var parts = path.slice(2).split('/'); + } + if (refPath[0] !== '#') { + var fullPath = path_1.resolve(path_1.join(this.filePath.dir, refPath)); + var file = fs_1.readFileSync(fullPath); + var targetType = this.toTsType(JSON.parse(file.toString())); + if (targetType.id) { + return new TsTypes_1.TsType.Literal(targetType.id); + } + else { + var parsedNewFile = path_1.parse(fullPath); + return new TsTypes_1.TsType.Literal(parsedNewFile.name); + } + } + ; + var parts = refPath.slice(2).split('/'); var ret = this.settings.declareReferenced ? this.declarations.get(parts.join('/')) : undefined; if (!ret) { var cur = this.schema; @@ -317,9 +366,9 @@ var Compiler = (function () { } return ret; }; - Compiler.prototype.declareType = function (type, path, id) { + Compiler.prototype.declareType = function (type, refPath, id) { type.id = id; - this.declarations.set(path, type); + this.declarations.set(refPath, type); return type; }; Compiler.prototype.toStringLiteral = function (a) { @@ -344,7 +393,18 @@ var Compiler = (function () { case RuleType.NamedSchema: return this.toTsDeclaration(rule); case RuleType.Enum: - return new TsTypes_1.TsType.Union(lodash_1.uniqBy(rule.enum.map(function (_) { return _this.toStringLiteral(_); }), function (_) { return _.toType(_this.settings); })); + if (this.settings.useTypescriptEnums) { + var enumValues = lodash_1.uniqBy(rule.enum.map(function (_) { return new TsTypes_1.TsType.Literal(_); }), function (_) { return _.toType(_this.settings); }); + if (rule.tsEnumNames) { + return new TsTypes_1.TsType.Enum(enumValues, rule.tsEnumNames.map(function (_) { return new TsTypes_1.TsType.Literal(_); })); + } + else { + return new TsTypes_1.TsType.Enum(enumValues); + } + } + else { + return new TsTypes_1.TsType.Union(lodash_1.uniqBy(rule.enum.map(function (_) { return _this.toStringLiteral(_); }), function (_) { return _.toType(_this.settings); })); + } case RuleType.Any: return new TsTypes_1.TsType.Any; case RuleType.Literal: return new TsTypes_1.TsType.Literal(rule); case RuleType.TypedArray: return new TsTypes_1.TsType.Array(this.toTsType(rule.items)); @@ -406,8 +466,8 @@ var Compiler = (function () { }; return Compiler; }()); -function compile(schema, settings) { - return new Compiler(schema, settings).toString(); +function compile(schema, path, settings) { + return new Compiler(schema, path, settings).toString(); } exports.compile = compile; function compileFromFile(inputFilename) { @@ -417,14 +477,14 @@ function compileFromFile(inputFilename) { reject(err); } else { - resolve(compile(JSON.parse(data.toString()))); + resolve(compile(JSON.parse(data.toString()), inputFilename)); } }); }); } exports.compileFromFile = compileFromFile; -},{"./TsTypes":1,"./pretty-printer":3,"fs":undefined,"lodash":undefined}],3:[function(require,module,exports){ +},{"./TsTypes":1,"./pretty-printer":3,"fs":undefined,"lodash":undefined,"path":undefined}],3:[function(require,module,exports){ // from https://github.com/Microsoft/TypeScript/wiki/Using-the-Compiler-API#pretty-printer-using-the-ls-formatter "use strict"; var ts = require("typescript"); diff --git a/package.json b/package.json index 4b215b3b..cf1dfd4b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "json-schema-to-typescript", - "version": "1.1.0", + "version": "1.2.0", "description": "compile json schema to typescript typings", "main": "dist/index.js", "typings": "dist/index.d.ts", diff --git a/src/JSONSchema.ts b/src/JSONSchema.ts index edb7b798..c4abd28e 100644 --- a/src/JSONSchema.ts +++ b/src/JSONSchema.ts @@ -38,6 +38,10 @@ export namespace JSONSchema { [k: string]: HttpJsonSchemaOrgDraft04Schema | string[]; }; enum?: any[]; + + // schema extension to support numeric enums + tsEnumNames?: string[]; + type?: SimpleTypes | SimpleTypes[]; allOf?: HttpJsonSchemaOrgDraft04Schema[]; anyOf?: HttpJsonSchemaOrgDraft04Schema[]; diff --git a/src/TsTypes.ts b/src/TsTypes.ts index 84ac379d..dd069980 100644 --- a/src/TsTypes.ts +++ b/src/TsTypes.ts @@ -8,6 +8,8 @@ export type TsTypeSettings = { useFullReferencePathAsName?: boolean; // TODO declareProperties?: boolean; useInterfaceDeclaration?: boolean; + useTypescriptEnums?: boolean; + exportInterfaces?: boolean; endTypeWithSemicolon?: boolean; endPropertyWithSemicolon?: boolean; declarationDescription?: boolean; @@ -20,6 +22,8 @@ export var DEFAULT_SETTINGS: TsTypeSettings = { useFullReferencePathAsName: false, // declareProperties: false, useInterfaceDeclaration: false, + useTypescriptEnums: false, + exportInterfaces: false, endTypeWithSemicolon: true, endPropertyWithSemicolon: true, declarationDescription: true, @@ -130,6 +134,35 @@ export class Union extends Intersection { .join('|') } } +export class Enum extends Intersection { + constructor(protected data: TsType[], protected literals?: TsType[]) { + super(data) + } + isSimpleType() { return false; } + _type(settings: TsTypeSettings, declaration: boolean = false) { + let id = this.safeId(); + let literals = this.literals; + // TODO if literals exist, get literal[i] below and write it first + return declaration || !id ? `{ + ${this.data.map((_, i) => { + let literal: TsType; + let decl = ''; + if(literals){ + literal = literals[i]; + decl += literal; + decl += '=' + } + decl += _.toType(settings) + decl += ','; + return decl; + }).join('\n')} + }` : id; + } + toDeclaration(settings: TsTypeSettings): string { + return `${this.toBlockComment(settings)}enum ${this.safeId()} ${this._type(settings, true)}`; + } +} + export class Interface extends TsType { constructor(private props: TsProp[]) { @@ -159,7 +192,7 @@ export class Interface extends TsType { isSimpleType() { return false; } toDeclaration(settings: TsTypeSettings): string { if (settings.useInterfaceDeclaration) - return `${this.toBlockComment(settings)}interface ${this.safeId()} ${this._type(settings, true)}` + return `${this.toBlockComment(settings)}${settings.exportInterfaces ? "export " : ""}interface ${this.safeId()} ${this._type(settings, true)}`; else return this._toDeclaration(`type ${this.safeId()} = ${this._type(settings, true)}`, settings); } diff --git a/src/index.ts b/src/index.ts index 8f47a60a..b463ce00 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,7 @@ import {camelCase, isPlainObject, last, map, merge, uniqBy, upperFirst} from 'lodash' import {JSONSchema} from './JSONSchema' -import {readFile} from 'fs' +import {readFile, readFileSync} from 'fs' +import {dirname, join, parse, resolve, ParsedPath} from 'path' import {Readable} from 'stream' import {format} from './pretty-printer' import {TsType} from './TsTypes' @@ -18,9 +19,11 @@ class Compiler { type: 'object' } - constructor(private schema: JSONSchema.Schema, settings?: TsType.TsTypeSettings) { - this.id = schema.id || schema.title || "Interface1"; + constructor(private schema: JSONSchema.Schema, filePath: string, settings?: TsType.TsTypeSettings) { + let path = resolve(filePath); + this.filePath = parse(path); this.declarations = new Map(); + this.id = schema.id || schema.title || this.filePath.name || "Interface1"; this.settings = Object.assign({}, Compiler.DEFAULT_SETTINGS, settings); let decl = this.declareType(this.toTsType(this.schema), this.id, this.id); } @@ -36,6 +39,7 @@ class Compiler { private settings: TsType.TsTypeSettings; private id: string; private declarations: Map; + private filePath: ParsedPath; private isRequired(propertyName: string, schema: JSONSchema.Schema): boolean { return schema.required ? schema.required.indexOf(propertyName) > -1 : false; @@ -93,12 +97,24 @@ class Compiler { } // eg. "#/definitions/diskDevice" => ["definitions", "diskDevice"] - private resolveType(path: string): TsType.TsType { - if (path[0] !== '#') - throw new Error("reference must start with #"); - if (path === '#' || path === '#/') + private resolveType(refPath: string): TsType.TsType { + if (refPath === '#' || refPath === '#/'){ return TsType.Interface.reference(this.id); - const parts = path.slice(2).split('/'); + } + + if (refPath[0] !== '#'){ + let fullPath = resolve(join(this.filePath.dir, refPath)); + let file = readFileSync(fullPath); + let targetType = this.toTsType(JSON.parse(file.toString())); + if(targetType.id){ + return new TsType.Literal(targetType.id); + } else { + let parsedNewFile = parse(fullPath); + return new TsType.Literal(parsedNewFile.name); + } + }; + + const parts = refPath.slice(2).split('/'); let ret = this.settings.declareReferenced ? this.declarations.get(parts.join('/')) : undefined; if (!ret) { let cur: any = this.schema; @@ -113,9 +129,9 @@ class Compiler { return ret; } - private declareType(type: TsType.TsType, path: string, id: string) { + private declareType(type: TsType.TsType, refPath: string, id: string) { type.id = id; - this.declarations.set(path, type); + this.declarations.set(refPath, type); return type; } @@ -145,10 +161,24 @@ class Compiler { case RuleType.NamedSchema: return this.toTsDeclaration(rule); case RuleType.Enum: - return new TsType.Union(uniqBy( - rule.enum!.map(_ => this.toStringLiteral(_)) - , _ => _.toType(this.settings)) - ) + // TODO: honor the schema's "type" on the enum. if string, + // skip all this mess; if int, either require the tsEnumNames + // or generate literals for the values + if(this.settings.useTypescriptEnums){ + let enumValues = uniqBy( + rule.enum!.map(_ => new TsType.Literal(_)) + , _ => _.toType(this.settings)) + if(rule.tsEnumNames){ + return new TsType.Enum(enumValues, + rule.tsEnumNames!.map(_ => new TsType.Literal(_))); + } else { + return new TsType.Enum(enumValues); + } + } else { + return new TsType.Union(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!)) @@ -204,8 +234,8 @@ class Compiler { } } -export function compile(schema: JSONSchema.Schema, settings?: TsType.TsTypeSettings): string { - return new Compiler(schema, settings).toString() +export function compile(schema: JSONSchema.Schema, path: string, settings?: TsType.TsTypeSettings): string { + return new Compiler(schema, path, settings).toString() } export function compileFromFile(inputFilename: string): Promise { @@ -214,7 +244,7 @@ export function compileFromFile(inputFilename: string): Promise { if (err) { reject(err) } else { - resolve(compile(JSON.parse(data.toString()))) + resolve(compile(JSON.parse(data.toString()), inputFilename)) } }) ) From 2c078b15b9829bf3340381029d9ea802e839d668 Mon Sep 17 00:00:00 2001 From: Jonathan Ogilvie Date: Thu, 25 Aug 2016 17:14:43 -0400 Subject: [PATCH 02/27] Exporting ts enum types if exporting is enabled --- dist/index.js | 6 ++++-- src/TsTypes.ts | 3 +-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/dist/index.js b/dist/index.js index c50779e4..df9d5fa3 100644 --- a/dist/index.js +++ b/dist/index.js @@ -184,7 +184,6 @@ var TsType; if (declaration === void 0) { declaration = false; } var id = this.safeId(); var literals = this.literals; - // TODO if literals exist, get literal[i] below and write it first return declaration || !id ? "{\n " + this.data.map(function (_, i) { var literal; var decl = ''; @@ -199,7 +198,7 @@ var TsType; }).join('\n') + "\n }" : id; }; Enum.prototype.toDeclaration = function (settings) { - return this.toBlockComment(settings) + "enum " + this.safeId() + " " + this._type(settings, true); + return "" + this.toBlockComment(settings) + (settings.exportInterfaces ? "export " : "") + "enum " + this.safeId() + " " + this._type(settings, true); }; return Enum; }(Intersection)); @@ -393,6 +392,9 @@ var Compiler = (function () { case RuleType.NamedSchema: return this.toTsDeclaration(rule); case RuleType.Enum: + // TODO: honor the schema's "type" on the enum. if string, + // skip all this mess; if int, either require the tsEnumNames + // or generate literals for the values if (this.settings.useTypescriptEnums) { var enumValues = lodash_1.uniqBy(rule.enum.map(function (_) { return new TsTypes_1.TsType.Literal(_); }), function (_) { return _.toType(_this.settings); }); if (rule.tsEnumNames) { diff --git a/src/TsTypes.ts b/src/TsTypes.ts index dd069980..caff9f19 100644 --- a/src/TsTypes.ts +++ b/src/TsTypes.ts @@ -142,7 +142,6 @@ export class Enum extends Intersection { _type(settings: TsTypeSettings, declaration: boolean = false) { let id = this.safeId(); let literals = this.literals; - // TODO if literals exist, get literal[i] below and write it first return declaration || !id ? `{ ${this.data.map((_, i) => { let literal: TsType; @@ -159,7 +158,7 @@ export class Enum extends Intersection { }` : id; } toDeclaration(settings: TsTypeSettings): string { - return `${this.toBlockComment(settings)}enum ${this.safeId()} ${this._type(settings, true)}`; + return `${this.toBlockComment(settings)}${settings.exportInterfaces ? "export " : ""}enum ${this.safeId()} ${this._type(settings, true)}`; } } From b68a46cc6145c6eb1b58b47f3144316c6b375ee8 Mon Sep 17 00:00:00 2001 From: Jonathan Ogilvie Date: Fri, 26 Aug 2016 13:25:38 -0400 Subject: [PATCH 03/27] Adding internal publication --- package.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/package.json b/package.json index cf1dfd4b..baa152fd 100644 --- a/package.json +++ b/package.json @@ -47,5 +47,8 @@ "gulp": "^3.9.1", "mocha": "^2.5.3", "watch": "^0.17.1" + }, + "publishConfig": { + "registry": "https://purecloud.artifactoryonline.com/purecloud/api/npm/inin-internal-npm-dev/" } } From 027fd5934cd83d0fde3ff6916641f2cf390ddf86 Mon Sep 17 00:00:00 2001 From: Jonathan Ogilvie Date: Mon, 29 Aug 2016 12:29:31 -0400 Subject: [PATCH 04/27] Improved ts enum support for inline enums --- dist/index.d.ts | 17 +++++-- dist/index.js | 116 +++++++++++++++++++++++++++++++-------------- src/TsTypes.ts | 51 +++++++++++++++++--- src/index.ts | 53 ++++++++++++++------- test/cases/enum.ts | 25 +++++++++- 5 files changed, 196 insertions(+), 66 deletions(-) diff --git a/dist/index.d.ts b/dist/index.d.ts index 15f45fd0..35540556 100644 --- a/dist/index.d.ts +++ b/dist/index.d.ts @@ -124,12 +124,19 @@ export namespace TsType { isSimpleType(): boolean; _type(settings: TsTypeSettings): string; } - class Enum extends Intersection { - protected data: TsType[]; - protected literals?: TsType[]; - constructor(data: TsType[], literals?: TsType[]); + class EnumValue { + identifier: string; + value: string; + constructor(enumValues: string[]); + toDeclaration(): string; + toString(): string; + } + class Enum extends TsType { + protected enumValues: EnumValue[]; + constructor(enumValues: EnumValue[]); isSimpleType(): boolean; - _type(settings: TsTypeSettings, declaration?: boolean): string; + _type(settings: TsTypeSettings): string; + toSafeType(settings: TsTypeSettings): string; toDeclaration(settings: TsTypeSettings): string; } class Interface extends TsType { diff --git a/dist/index.js b/dist/index.js index df9d5fa3..d7a38319 100644 --- a/dist/index.js +++ b/dist/index.js @@ -172,36 +172,66 @@ var TsType; return Union; }(Intersection)); TsType_1.Union = Union; + var EnumValue = (function () { + function EnumValue(enumValues) { + var hasValue = !!enumValues[0]; + // quirky propagation logic + if (hasValue) { + this.identifier = enumValues[0]; + this.value = enumValues[1]; + } + else { + this.identifier = enumValues[1]; + } + } + EnumValue.prototype.toDeclaration = function () { + // if there is a value associated with the identifier, declare as identifier=value + // else declare as identifier + return "" + this.identifier + (this.value ? ('=' + this.value) : ''); + }; + EnumValue.prototype.toString = function () { + return "Enum" + this.identifier; + }; + return EnumValue; + }()); + TsType_1.EnumValue = EnumValue; var Enum = (function (_super) { __extends(Enum, _super); - function Enum(data, literals) { - _super.call(this, data); - this.data = data; - this.literals = literals; + function Enum(enumValues) { + _super.call(this); + this.enumValues = enumValues; } Enum.prototype.isSimpleType = function () { return false; }; - Enum.prototype._type = function (settings, declaration) { - if (declaration === void 0) { declaration = false; } - var id = this.safeId(); - var literals = this.literals; - return declaration || !id ? "{\n " + this.data.map(function (_, i) { - var literal; - var decl = ''; - if (literals) { + Enum.prototype._type = function (settings) { + return this.safeId() || "SomeEnumType"; + /* + let literals = this.literals; + + // if this is a declaration, or this type has no ID (??) + // then we reference it in-line? not really acceptable for this case + return declaration || !id ? `{ + ${this.data.map((_, i) => { + let literal: TsType; + let decl = ''; + if(literals){ literal = literals[i]; decl += literal; - decl += '='; - } - decl += _.toType(settings); - decl += ','; - return decl; - }).join('\n') + "\n }" : id; + decl += '=' + } + decl += _.toType(settings) + decl += ','; + return decl; + }).join('\n')} + }` : id;*/ + }; + Enum.prototype.toSafeType = function (settings) { + return "" + this.toType(settings); }; Enum.prototype.toDeclaration = function (settings) { - return "" + this.toBlockComment(settings) + (settings.exportInterfaces ? "export " : "") + "enum " + this.safeId() + " " + this._type(settings, true); + return "" + this.toBlockComment(settings) + (settings.exportInterfaces ? "export " : "") + "enum " + this._type(settings) + "{\n " + this.enumValues.map(function (_) { return _.toDeclaration(); }).join(',\n') + "\n }"; }; return Enum; - }(Intersection)); + }(TsType)); TsType_1.Enum = Enum; var Interface = (function (_super) { __extends(Interface, _super); @@ -274,7 +304,7 @@ var Compiler = (function () { this.declarations = new Map(); this.id = schema.id || schema.title || this.filePath.name || "Interface1"; this.settings = Object.assign({}, Compiler.DEFAULT_SETTINGS, settings); - var decl = this.declareType(this.toTsType(this.schema), this.id, this.id); + var decl = this.declareType(this.toTsType(this.schema, '', true), this.id, this.id); } Compiler.prototype.toString = function () { var _this = this; @@ -295,6 +325,7 @@ var Compiler = (function () { if (rule.type === 'array' && rule.items) { return RuleType.TypedArray; } + // enum type vs enum constant? if (rule.enum) { return RuleType.Enum; } @@ -334,14 +365,15 @@ var Compiler = (function () { return /^[\d\.]+$/.test(a); }; // eg. "#/definitions/diskDevice" => ["definitions", "diskDevice"] - Compiler.prototype.resolveType = function (refPath) { + // only called in case of a $ref type + Compiler.prototype.resolveType = function (refPath, propName) { if (refPath === '#' || refPath === '#/') { return TsTypes_1.TsType.Interface.reference(this.id); } if (refPath[0] !== '#') { var fullPath = path_1.resolve(path_1.join(this.filePath.dir, refPath)); var file = fs_1.readFileSync(fullPath); - var targetType = this.toTsType(JSON.parse(file.toString())); + var targetType = this.toTsType(JSON.parse(file.toString()), propName, false, true); if (targetType.id) { return new TsTypes_1.TsType.Literal(targetType.id); } @@ -365,6 +397,9 @@ var Compiler = (function () { } return ret; }; + Compiler.prototype.isReference = function (schema) { + return schema.$ref; // && !schema.$ref.startsWith('#'); + }; Compiler.prototype.declareType = function (type, refPath, id) { type.id = id; this.declarations.set(refPath, type); @@ -385,24 +420,33 @@ var Compiler = (function () { })); } }; - Compiler.prototype.createTsType = function (rule) { + Compiler.prototype.createTsType = function (rule, propName, isTop, isReference) { var _this = this; + if (isTop === void 0) { isTop = false; } + if (isReference === void 0) { isReference = false; } switch (this.getRuleType(rule)) { case RuleType.AnonymousSchema: case RuleType.NamedSchema: return this.toTsDeclaration(rule); case RuleType.Enum: // TODO: honor the schema's "type" on the enum. if string, - // skip all this mess; if int, either require the tsEnumNames + // skip all the zipping mess; if int, either require the tsEnumNames // or generate literals for the values + // TODO: what to do in the case where the value is an Object? + // right now we just pring [Object object] as the literal which is bad if (this.settings.useTypescriptEnums) { - var enumValues = lodash_1.uniqBy(rule.enum.map(function (_) { return new TsTypes_1.TsType.Literal(_); }), function (_) { return _.toType(_this.settings); }); - if (rule.tsEnumNames) { - return new TsTypes_1.TsType.Enum(enumValues, rule.tsEnumNames.map(function (_) { return new TsTypes_1.TsType.Literal(_); })); - } - else { - return new TsTypes_1.TsType.Enum(enumValues); + var enumValues = lodash_1.zip(rule.tsEnumNames || [], rule.enum.map(function (_) { return new TsTypes_1.TsType.Literal(_).toType(_this.settings); })) + .map(function (_) { return new TsTypes_1.TsType.EnumValue(_); }); + // TODO: how do I get the property name under which this was declared + // (or the file name, failing that)? that would make a much better name here. + var path = rule.id || propName || ("Enum" + enumValues.map(function (_) { return _.identifier; }).join("")); + var retVal = new TsTypes_1.TsType.Enum(enumValues); + // don't add this to the declarations map if this is the top-level type (already declared) + // or if it's a reference and we don't want to declare those. + if (!isTop && (!isReference || this.settings.declareReferenced)) { + retVal = this.declareType(retVal, path, path); } + return retVal; } else { return new TsTypes_1.TsType.Union(lodash_1.uniqBy(rule.enum.map(function (_) { return _this.toStringLiteral(_); }), function (_) { return _.toType(_this.settings); })); @@ -421,12 +465,14 @@ var Compiler = (function () { case RuleType.AnyOf: return new TsTypes_1.TsType.Union(rule.anyOf.map(function (_) { return _this.toTsType(_); })); case RuleType.Reference: - return this.resolveType(rule.$ref); + return this.resolveType(rule.$ref, propName); } throw "bug"; }; - Compiler.prototype.toTsType = function (rule) { - var type = this.createTsType(rule); + Compiler.prototype.toTsType = function (rule, propName, isTop, isReference) { + if (isTop === void 0) { isTop = false; } + if (isReference === void 0) { isReference = false; } + var type = this.createTsType(rule, propName, isTop, isReference); type.id = type.id || rule.id || rule.title; type.description = type.description || rule.description; return type; @@ -437,7 +483,7 @@ var Compiler = (function () { var set = new Set(); var props = lodash_1.map(copy.properties, function (v, k) { return { - type: _this.toTsType(v), + type: _this.toTsType(v, k), name: k, required: _this.isRequired(k, copy) }; diff --git a/src/TsTypes.ts b/src/TsTypes.ts index caff9f19..acd477f3 100644 --- a/src/TsTypes.ts +++ b/src/TsTypes.ts @@ -134,14 +134,46 @@ export class Union extends Intersection { .join('|') } } -export class Enum extends Intersection { - constructor(protected data: TsType[], protected literals?: TsType[]) { - super(data) + +export class EnumValue { + identifier: string; + value: string; + + constructor(enumValues: string[]) { + let hasValue = !!enumValues[0]; + + // quirky propagation logic + if(hasValue){ + this.identifier = enumValues[0]; + this.value = enumValues[1]; + } else { + this.identifier = enumValues[1]; + } + } + + toDeclaration(){ + // if there is a value associated with the identifier, declare as identifier=value + // else declare as identifier + return `${this.identifier}${this.value ? ('=' + this.value) : ''}` + } + + toString(){ + return `Enum${this.identifier}` + } +} + +export class Enum extends TsType { + constructor(protected enumValues: EnumValue[]) { + super() } isSimpleType() { return false; } - _type(settings: TsTypeSettings, declaration: boolean = false) { - let id = this.safeId(); + _type(settings: TsTypeSettings) { + return this.safeId() || "SomeEnumType"; + /* let literals = this.literals; + + // if this is a declaration, or this type has no ID (??) + // then we reference it in-line? not really acceptable for this case return declaration || !id ? `{ ${this.data.map((_, i) => { let literal: TsType; @@ -155,10 +187,15 @@ export class Enum extends Intersection { decl += ','; return decl; }).join('\n')} - }` : id; + }` : id;*/ + } + toSafeType(settings: TsTypeSettings) { + return `${this.toType(settings)}`; } toDeclaration(settings: TsTypeSettings): string { - return `${this.toBlockComment(settings)}${settings.exportInterfaces ? "export " : ""}enum ${this.safeId()} ${this._type(settings, true)}`; + return `${this.toBlockComment(settings)}${settings.exportInterfaces ? "export " : ""}enum ${this._type(settings)}{ + ${this.enumValues.map(_ => _.toDeclaration()).join(',\n')} + }`; } } diff --git a/src/index.ts b/src/index.ts index b463ce00..9738fe0c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,4 @@ -import {camelCase, isPlainObject, last, map, merge, uniqBy, upperFirst} from 'lodash' +import {camelCase, isPlainObject, last, map, merge, uniqBy, upperFirst, zip} from 'lodash' import {JSONSchema} from './JSONSchema' import {readFile, readFileSync} from 'fs' import {dirname, join, parse, resolve, ParsedPath} from 'path' @@ -25,7 +25,7 @@ class Compiler { this.declarations = new Map(); this.id = schema.id || schema.title || this.filePath.name || "Interface1"; this.settings = Object.assign({}, Compiler.DEFAULT_SETTINGS, settings); - let decl = this.declareType(this.toTsType(this.schema), this.id, this.id); + let decl = this.declareType(this.toTsType(this.schema, '', true), this.id, this.id); } toString(): string { @@ -57,6 +57,7 @@ class Compiler { if (rule.type === 'array' && rule.items) { return RuleType.TypedArray } + // enum type vs enum constant? if (rule.enum) { return RuleType.Enum } @@ -97,7 +98,8 @@ class Compiler { } // eg. "#/definitions/diskDevice" => ["definitions", "diskDevice"] - private resolveType(refPath: string): TsType.TsType { + // only called in case of a $ref type + private resolveType(refPath: string, propName: string): TsType.TsType { if (refPath === '#' || refPath === '#/'){ return TsType.Interface.reference(this.id); } @@ -105,7 +107,7 @@ class Compiler { if (refPath[0] !== '#'){ let fullPath = resolve(join(this.filePath.dir, refPath)); let file = readFileSync(fullPath); - let targetType = this.toTsType(JSON.parse(file.toString())); + let targetType = this.toTsType(JSON.parse(file.toString()), propName, false, true); if(targetType.id){ return new TsType.Literal(targetType.id); } else { @@ -129,6 +131,10 @@ class Compiler { return ret; } + private isReference(schema: JSONSchema.Schema){ + return schema.$ref; // && !schema.$ref.startsWith('#'); + } + private declareType(type: TsType.TsType, refPath: string, id: string) { type.id = id; this.declarations.set(refPath, type); @@ -155,25 +161,35 @@ class Compiler { } } - private createTsType (rule: JSONSchema.Schema): TsType.TsType { + private createTsType (rule: JSONSchema.Schema, propName?: string, isTop: boolean = false, isReference: boolean = false): TsType.TsType { switch (this.getRuleType(rule)) { case RuleType.AnonymousSchema: case RuleType.NamedSchema: return this.toTsDeclaration(rule); case RuleType.Enum: // TODO: honor the schema's "type" on the enum. if string, - // skip all this mess; if int, either require the tsEnumNames + // skip all the zipping mess; if int, either require the tsEnumNames // or generate literals for the values + + // TODO: what to do in the case where the value is an Object? + // right now we just pring [Object object] as the literal which is bad if(this.settings.useTypescriptEnums){ - let enumValues = uniqBy( - rule.enum!.map(_ => new TsType.Literal(_)) - , _ => _.toType(this.settings)) - if(rule.tsEnumNames){ - return new TsType.Enum(enumValues, - rule.tsEnumNames!.map(_ => new TsType.Literal(_))); - } else { - return new TsType.Enum(enumValues); + var enumValues = zip(rule.tsEnumNames || [], + rule.enum!.map(_ => new TsType.Literal(_).toType(this.settings))) + .map(_ => new TsType.EnumValue(_)); + + // TODO: how do I get the property name under which this was declared + // (or the file name, failing that)? that would make a much better name here. + let path = rule.id || propName || ("Enum" + enumValues.map(_ => _.identifier).join("")); + let retVal: TsType.TsType = new TsType.Enum(enumValues); + + // don't add this to the declarations map if this is the top-level type (already declared) + // or if it's a reference and we don't want to declare those. + if(!isTop && (!isReference || this.settings.declareReferenced)){ + retVal = this.declareType(retVal, path, path); } + return retVal; + } else { return new TsType.Union(uniqBy( rule.enum!.map(_ => this.toStringLiteral(_)) @@ -193,12 +209,13 @@ class Compiler { case RuleType.AnyOf: return new TsType.Union(rule.anyOf!.map(_ => this.toTsType(_))) case RuleType.Reference: - return this.resolveType(rule.$ref!); + return this.resolveType(rule.$ref!, propName!); } throw "bug"; } - private toTsType (rule: JSONSchema.Schema): TsType.TsType { - let type = this.createTsType(rule); + + private toTsType (rule: JSONSchema.Schema, propName?: string, isTop: boolean = false, isReference: boolean = false): TsType.TsType { + let type = this.createTsType(rule, propName, isTop, isReference); type.id = type.id || rule.id || rule.title; type.description = type.description || rule.description; return type; @@ -210,7 +227,7 @@ class Compiler { copy.properties!, (v: JSONSchema.Schema, k: string) => { return { - type: this.toTsType(v), + type: this.toTsType(v, k), name: k, required: this.isRequired(k, copy) }; diff --git a/test/cases/enum.ts b/test/cases/enum.ts index 6a71122d..33695425 100644 --- a/test/cases/enum.ts +++ b/test/cases/enum.ts @@ -7,7 +7,8 @@ export var schema = "enum": ["a", "b", "c"] }, "bar": { - "enum": [1, 2, 3] + "enum": [1, 2, 3], + "tsEnumNames": ["One","Two","Three"] }, "baz": { "enum": [ @@ -21,6 +22,28 @@ export var schema = "additionalProperties": false } +export var configurations = [ + { + settings: { + useTypescriptEnums: false + }, + types: `type Enum = { + foo: "a" | "b" | "c"; + bar: number; + baz: { + a: number; + }; +};` + }, + { + settings: { + useTypescriptEnums: true, + + }, + types: `` + } +] + export var types = `type Enum = { foo: "a" | "b" | "c"; bar: number; From effc66396018ce00c66755fd77969a0ebd4a1b70 Mon Sep 17 00:00:00 2001 From: Jonathan Ogilvie Date: Mon, 29 Aug 2016 14:18:51 -0400 Subject: [PATCH 05/27] Bumping version; updating tests --- package.json | 2 +- test/cases/basics.ts | 15 +++++++++++++++ test/cases/enum.ts | 34 ++++++++++++++++++++++------------ test/cases/unnamedSchema.ts | 2 +- test/test.ts | 4 ++-- 5 files changed, 41 insertions(+), 16 deletions(-) diff --git a/package.json b/package.json index baa152fd..d27a2bce 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "json-schema-to-typescript", - "version": "1.2.0", + "version": "1.2.1", "description": "compile json schema to typescript typings", "main": "dist/index.js", "typings": "dist/index.d.ts", diff --git a/test/cases/basics.ts b/test/cases/basics.ts index d35b315a..58dbf116 100644 --- a/test/cases/basics.ts +++ b/test/cases/basics.ts @@ -40,6 +40,21 @@ export var configurations = [ favoriteFoods?: any[]; likesDogs?: boolean; [k: string]: any; +}` + }, + { + settings: { + useInterfaceDeclaration: true, + exportInterfaces: true + }, + types: `export interface ExampleSchema { + firstName: string; + lastName: string; + age?: number; // Age in years + height?: number; + favoriteFoods?: any[]; + likesDogs?: boolean; + [k: string]: any; }` }, { diff --git a/test/cases/enum.ts b/test/cases/enum.ts index 33695425..5117aa13 100644 --- a/test/cases/enum.ts +++ b/test/cases/enum.ts @@ -37,17 +37,27 @@ export var configurations = [ }, { settings: { - useTypescriptEnums: true, - + useTypescriptEnums: true }, - types: `` + types: `enum Foo { + a, + b, + c +} +enum Bar { + One = 1, + Two = 2, + Three = 3 +} +enum Baz { + [object Object], + [object Object], + [object Object] +} +type Enum = { + foo: Foo; + bar: Bar; + baz: Baz; +};` } -] - -export var types = `type Enum = { - foo: "a" | "b" | "c"; - bar: number; - baz: { - a: number; - }; -};` \ No newline at end of file +] \ No newline at end of file diff --git a/test/cases/unnamedSchema.ts b/test/cases/unnamedSchema.ts index 910e546c..6db61bf0 100644 --- a/test/cases/unnamedSchema.ts +++ b/test/cases/unnamedSchema.ts @@ -10,6 +10,6 @@ export var schema = "additionalProperties": false } -export var types = `type Interface1 = { +export var types = `type UnnamedSchema = { foo: string; };` \ No newline at end of file diff --git a/test/test.ts b/test/test.ts index 6f2a5899..5a656b6e 100644 --- a/test/test.ts +++ b/test/test.ts @@ -22,7 +22,7 @@ modules.forEach((exports, name) => { describe(name, function() { exports.configurations.forEach((cfg: any) => { it(JSON.stringify(cfg.settings), () => { - expect(compile(exports.schema, cfg.settings)).to.be.equal(cfg.types); + expect(compile(exports.schema, name, cfg.settings)).to.be.equal(cfg.types); }); }); }); @@ -30,7 +30,7 @@ modules.forEach((exports, name) => { else { describe(name, function() { it("default settings", () => { - expect(compile(exports.schema, exports.settings)).to.be.equal(exports.types); + expect(compile(exports.schema, name, exports.settings)).to.be.equal(exports.types); }); }); } From 8ddda5e0b9f02453fe75b9256b36f86f85df51e4 Mon Sep 17 00:00:00 2001 From: Jonathan Ogilvie Date: Mon, 29 Aug 2016 15:02:26 -0400 Subject: [PATCH 06/27] Adding file ref test --- .vscode/launch.json | 23 +++++++++++++++++ dist/index.js | 29 ++++++---------------- src/TsTypes.ts | 19 -------------- src/index.ts | 12 +++++++-- test/cases/ref.ts | 40 ++++++++++++++++++++++++++++++ test/resources/ReferencedType.json | 27 ++++++++++++++++++++ 6 files changed, 108 insertions(+), 42 deletions(-) create mode 100644 .vscode/launch.json create mode 100644 test/cases/ref.ts create mode 100644 test/resources/ReferencedType.json diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 00000000..34fd8a53 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,23 @@ +{ + "version": "0.2.5", + "configurations": [ + { + // Name of configuration; appears in the launch configuration drop down menu. + "name": "Run mocha", + // Type of configuration. Possible values: "node", "mono". + "type": "node", + // Workspace relative or absolute path to the program. + "program": "${workspaceRoot}/node_modules/mocha/bin/_mocha", + // Automatically stop program after launch. + "stopOnEntry": false, + // Command line arguments passed to the program. + "args": ["out/test/test.js"], + // Workspace relative or absolute path to the working directory of the program being debugged. Default is the current workspace. + "cwd": "${workspaceRoot}", + // Workspace relative or absolute path to the runtime executable to be used. Default is the runtime executable on the PATH. + "runtimeExecutable": null, + // Environment variables passed to the program. + "env": { "NODE_ENV": "production"} +} + ] +} \ No newline at end of file diff --git a/dist/index.js b/dist/index.js index d7a38319..3272ee55 100644 --- a/dist/index.js +++ b/dist/index.js @@ -204,25 +204,6 @@ var TsType; Enum.prototype.isSimpleType = function () { return false; }; Enum.prototype._type = function (settings) { return this.safeId() || "SomeEnumType"; - /* - let literals = this.literals; - - // if this is a declaration, or this type has no ID (??) - // then we reference it in-line? not really acceptable for this case - return declaration || !id ? `{ - ${this.data.map((_, i) => { - let literal: TsType; - let decl = ''; - if(literals){ - literal = literals[i]; - decl += literal; - decl += '=' - } - decl += _.toType(settings) - decl += ','; - return decl; - }).join('\n')} - }` : id;*/ }; Enum.prototype.toSafeType = function (settings) { return "" + this.toType(settings); @@ -371,16 +352,22 @@ var Compiler = (function () { return TsTypes_1.TsType.Interface.reference(this.id); } if (refPath[0] !== '#') { + var retVal = void 0; + var id = void 0; var fullPath = path_1.resolve(path_1.join(this.filePath.dir, refPath)); var file = fs_1.readFileSync(fullPath); var targetType = this.toTsType(JSON.parse(file.toString()), propName, false, true); if (targetType.id) { - return new TsTypes_1.TsType.Literal(targetType.id); + id = targetType.toSafeType(this.settings); } else { var parsedNewFile = path_1.parse(fullPath); - return new TsTypes_1.TsType.Literal(parsedNewFile.name); + id = parsedNewFile.name; } + if (this.settings.declareReferenced) { + this.declareType(targetType, id, id); + } + return new TsTypes_1.TsType.Literal(id); } ; var parts = refPath.slice(2).split('/'); diff --git a/src/TsTypes.ts b/src/TsTypes.ts index acd477f3..3c5a09e2 100644 --- a/src/TsTypes.ts +++ b/src/TsTypes.ts @@ -169,25 +169,6 @@ export class Enum extends TsType { isSimpleType() { return false; } _type(settings: TsTypeSettings) { return this.safeId() || "SomeEnumType"; - /* - let literals = this.literals; - - // if this is a declaration, or this type has no ID (??) - // then we reference it in-line? not really acceptable for this case - return declaration || !id ? `{ - ${this.data.map((_, i) => { - let literal: TsType; - let decl = ''; - if(literals){ - literal = literals[i]; - decl += literal; - decl += '=' - } - decl += _.toType(settings) - decl += ','; - return decl; - }).join('\n')} - }` : id;*/ } toSafeType(settings: TsTypeSettings) { return `${this.toType(settings)}`; diff --git a/src/index.ts b/src/index.ts index 9738fe0c..6cc19ded 100644 --- a/src/index.ts +++ b/src/index.ts @@ -105,15 +105,23 @@ class Compiler { } if (refPath[0] !== '#'){ + let retVal: TsType.TsType; + let id: string; let fullPath = resolve(join(this.filePath.dir, refPath)); let file = readFileSync(fullPath); let targetType = this.toTsType(JSON.parse(file.toString()), propName, false, true); if(targetType.id){ - return new TsType.Literal(targetType.id); + id = targetType.toSafeType(this.settings); } else { let parsedNewFile = parse(fullPath); - return new TsType.Literal(parsedNewFile.name); + id = parsedNewFile.name; } + + if(this.settings.declareReferenced){ + this.declareType(targetType, id, id); + } + + return new TsType.Literal(id); }; const parts = refPath.slice(2).split('/'); diff --git a/test/cases/ref.ts b/test/cases/ref.ts new file mode 100644 index 00000000..12770b4b --- /dev/null +++ b/test/cases/ref.ts @@ -0,0 +1,40 @@ +export var schema = +{ + "title": "Referencing", + "type": "object", + "properties": { + "foo": { + "$ref": "test/resources/ReferencedType.json" + } + }, + "required": ["foo"], + "additionalProperties": false +} + +export var configurations = [ + { + settings: { + declareReferenced: true + }, + types: `type ExampleSchema = { + firstName: string; + lastName: string; + age?: number; // Age in years + height?: number; + favoriteFoods?: any[]; + likesDogs?: boolean; + [k: string]: any; +}; +type Referencing = { + foo: ExampleSchema; +};` + }, + { + settings: { + declareReferenced: false + }, + types: `type Referencing = { + foo: ExampleSchema; +};` + } +] \ No newline at end of file diff --git a/test/resources/ReferencedType.json b/test/resources/ReferencedType.json new file mode 100644 index 00000000..8e9e062c --- /dev/null +++ b/test/resources/ReferencedType.json @@ -0,0 +1,27 @@ +{ + "title": "Example Schema", + "type": "object", + "properties": { + "firstName": { + "type": "string" + }, + "lastName": { + "type": "string" + }, + "age": { + "description": "Age in years", + "type": "integer", + "minimum": 0 + }, + "height": { + "type": "number" + }, + "favoriteFoods": { + "type": "array" + }, + "likesDogs": { + "type": "boolean" + } + }, + "required": ["firstName", "lastName"] +} \ No newline at end of file From 01b732b8ff2ef7acc80dd74e2a1f6e438e7c23c5 Mon Sep 17 00:00:00 2001 From: Jonathan Ogilvie Date: Mon, 29 Aug 2016 15:15:37 -0400 Subject: [PATCH 07/27] Undoing publish change --- package.json | 3 --- 1 file changed, 3 deletions(-) diff --git a/package.json b/package.json index d27a2bce..e4394a4d 100644 --- a/package.json +++ b/package.json @@ -47,8 +47,5 @@ "gulp": "^3.9.1", "mocha": "^2.5.3", "watch": "^0.17.1" - }, - "publishConfig": { - "registry": "https://purecloud.artifactoryonline.com/purecloud/api/npm/inin-internal-npm-dev/" } } From 99476e2040d783d1ea5661aa9022fbcbdf15439b Mon Sep 17 00:00:00 2001 From: Jonathan Ogilvie Date: Tue, 30 Aug 2016 11:46:16 -0400 Subject: [PATCH 08/27] Remove todo that was fixed --- src/index.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/index.ts b/src/index.ts index ad044a3c..71c8c3a3 100644 --- a/src/index.ts +++ b/src/index.ts @@ -180,8 +180,10 @@ class Compiler { rule.enum!.map(_ => new TsType.Literal(_).toType(this.settings))) .map(_ => new TsType.EnumValue(_)); - // TODO: how do I get the property name under which this was declared - // (or the file name, failing that)? that would make a much better name here. + // name our anonymous enum, if it doesn't have an ID, by the property name under + // which it was declared. Failing both of these things, it'll concat together the + // identifiers as EnumOneTwoThree for enum: ["One", "Two", "Three"]. Ugly, but + // practical. let path = rule.id || propName || ("Enum" + enumValues.map(_ => _.identifier).join("")); let retVal: TsType.TsType = new TsType.Enum(enumValues); From 723cdd103cbc078973011ded8279d41987bf7cbf Mon Sep 17 00:00:00 2001 From: Jonathan Ogilvie Date: Thu, 1 Sep 2016 17:00:43 -0400 Subject: [PATCH 09/27] v1.2.3: add generated typescript helpers --- dist/index.d.ts | 15 +++++++++++- dist/index.js | 61 ++++++++++++++++++++++++++++++++++++++++++---- package.json | 2 +- src/TsTypes.ts | 64 +++++++++++++++++++++++++++++++++++++++++++++++-- src/index.ts | 18 +++++++++++--- 5 files changed, 148 insertions(+), 12 deletions(-) diff --git a/dist/index.d.ts b/dist/index.d.ts index 5474aacb..ad75abc6 100644 --- a/dist/index.d.ts +++ b/dist/index.d.ts @@ -67,6 +67,7 @@ export namespace TsType { endPropertyWithSemicolon?: boolean; declarationDescription?: boolean; propertyDescription?: boolean; + addEnumUtils?: boolean; } var DEFAULT_SETTINGS: TsTypeSettings; abstract class TsType { @@ -117,13 +118,25 @@ export namespace TsType { toString(): string; } class Enum extends TsType { - protected enumValues: EnumValue[]; + enumValues: EnumValue[]; constructor(enumValues: EnumValue[]); isSimpleType(): boolean; _type(settings: TsTypeSettings): string; toSafeType(settings: TsTypeSettings): string; toDeclaration(settings: TsTypeSettings): string; } + class EnumUtils extends TsType { + protected enm: Enum; + constructor(enm: Enum); + isSimpleType(): boolean; + _type(settings: TsTypeSettings): string; + toSafeType(settings: TsTypeSettings): string; + toDeclaration(settings: TsTypeSettings): string; + makeValuesMethod(settings: TsTypeSettings): string; + makeFromStringValueMethod(settings: TsTypeSettings): string; + makeToStringValueMethod(settings: TsTypeSettings): string; + makeFromStringValuesMethod(settings: TsTypeSettings): string; + } class Array extends TsType { constructor(type?: TsType); _type(settings: TsTypeSettings): string; diff --git a/dist/index.js b/dist/index.js index 355a8ba6..65972c8a 100644 --- a/dist/index.js +++ b/dist/index.js @@ -20,6 +20,7 @@ var TsType; useInterfaceDeclaration: true, useTypescriptEnums: false, exportInterfaces: false, + addEnumUtils: false }; var TsType = (function () { function TsType() { @@ -169,6 +170,44 @@ var TsType; return Enum; }(TsType)); TsType_1.Enum = Enum; + var EnumUtils = (function (_super) { + __extends(EnumUtils, _super); + function EnumUtils(enm) { + _super.call(this); + this.enm = enm; + } + EnumUtils.prototype.isSimpleType = function () { return false; }; + EnumUtils.prototype._type = function (settings) { + // It's a bit hacky, but if this is a top level type, then addDeclaration changes + // our enum type's ID out from under us when it adds the enum to the declaration map, *after* + // the util class is declared. So we name ourselves by our enum's type, not our own ID' + return this.enm.toSafeType(settings) + "Util" || this.safeId() || "SomeEnumTypeUtils"; + }; + EnumUtils.prototype.toSafeType = function (settings) { + return "" + this.toType(settings); + }; + EnumUtils.prototype.toDeclaration = function (settings) { + return "" + this.toBlockComment(settings) + (settings.exportInterfaces ? "export " : "") + "class " + this._type(settings) + " {\n " + this.makeValuesMethod(settings) + "\n " + this.makeToStringValueMethod(settings) + "\n " + this.makeFromStringValueMethod(settings) + "\n " + this.makeFromStringValuesMethod(settings) + "\n }"; + }; + EnumUtils.prototype.makeValuesMethod = function (settings) { + var enumType = this.enm.toSafeType(settings); + return "static values(): " + enumType + "[] {\n return [" + this.enm.enumValues.map(function (_) { return (enumType + "." + _.identifier); }).join(',') + "]\n }"; + }; + EnumUtils.prototype.makeFromStringValueMethod = function (settings) { + var enumType = this.enm.toSafeType(settings); + return "static fromStringValue(value: string): " + enumType + " {\n switch(value.toLowerCase()){\n " + this.enm.enumValues.map(function (_) { return ("case \"" + _.identifier.toLowerCase() + "\":\n return " + (enumType + '.' + _.identifier) + ";"); }).join('\n') + "\n default:\n throw new Error(\"Unrecognized " + enumType + ": \" + value);\n }\n }"; + }; + EnumUtils.prototype.makeToStringValueMethod = function (settings) { + var enumType = this.enm.toSafeType(settings); + return "static toStringValue(enm: " + enumType + "): " + enumType + " {\n switch(enm.toLowerCase()){\n " + this.enm.enumValues.map(function (_) { return ("case " + (enumType + '.' + _.identifier) + ":\n return \"" + _.identifier.toLowerCase() + "\";"); }).join('\n') + "\n }\n }"; + }; + EnumUtils.prototype.makeFromStringValuesMethod = function (settings) { + var enumType = this.enm.toSafeType(settings); + return "static fromStringValues(values: string[]): " + enumType + "[] {\n return _.map(values, value => " + this._type(settings) + ".fromStringValue(value));\n }"; + }; + return EnumUtils; + }(TsType)); + TsType_1.EnumUtils = EnumUtils; var Array = (function (_super) { __extends(Array, _super); function Array(type) { @@ -417,14 +456,26 @@ var Compiler = (function () { if (this.settings.useTypescriptEnums) { var enumValues = lodash_1.zip(rule.tsEnumNames || [], rule.enum.map(function (_) { return new TsTypes_1.TsType.Literal(_).toType(_this.settings); })) .map(function (_) { return new TsTypes_1.TsType.EnumValue(_); }); - // TODO: how do I get the property name under which this was declared - // (or the file name, failing that)? that would make a much better name here. + // name our anonymous enum, if it doesn't have an ID, by the property name under + // which it was declared. Failing both of these things, it'll concat together the + // identifiers as EnumOneTwoThree for enum: ["One", "Two", "Three"]. Ugly, but + // practical. var path = rule.id || propName || ("Enum" + enumValues.map(function (_) { return _.identifier; }).join("")); - var retVal = new TsTypes_1.TsType.Enum(enumValues); + var enm = new TsTypes_1.TsType.Enum(enumValues); + var retVal = enm; // don't add this to the declarations map if this is the top-level type (already declared) // or if it's a reference and we don't want to declare those. - if (!isTop && (!isReference || this.settings.declareReferenced)) { - retVal = this.declareType(retVal, path, path); + if ((!isReference || this.settings.declareReferenced)) { + if (!isTop) { + retVal = this.declareType(retVal, path, path); + } + else { + retVal.id = path; + } + if (this.settings.addEnumUtils) { + var utilPath = path + "Utils"; + this.declareType(new TsTypes_1.TsType.EnumUtils(enm), utilPath, utilPath); + } } return retVal; } diff --git a/package.json b/package.json index 5ff040e8..53b87225 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "json-schema-to-typescript", - "version": "1.2.1", + "version": "1.2.3", "description": "compile json schema to typescript typings", "main": "dist/index.js", "typings": "dist/index.d.ts", diff --git a/src/TsTypes.ts b/src/TsTypes.ts index 70650c02..deea22a4 100644 --- a/src/TsTypes.ts +++ b/src/TsTypes.ts @@ -13,7 +13,8 @@ export namespace TsType { endTypeWithSemicolon?: boolean endPropertyWithSemicolon?: boolean declarationDescription?: boolean - propertyDescription?: boolean + propertyDescription?: boolean, + addEnumUtils?: boolean } export var DEFAULT_SETTINGS: TsTypeSettings = { @@ -28,6 +29,7 @@ export namespace TsType { useInterfaceDeclaration: true, useTypescriptEnums: false, exportInterfaces: false, + addEnumUtils: false } export abstract class TsType { @@ -130,7 +132,7 @@ export class EnumValue { } export class Enum extends TsType { - constructor(protected enumValues: EnumValue[]) { + constructor(public enumValues: EnumValue[]) { super() } isSimpleType() { return false; } @@ -145,6 +147,64 @@ export class Enum extends TsType { ${this.enumValues.map(_ => _.toDeclaration()).join(',\n')} }`; } +} + +export class EnumUtils extends TsType { + constructor(protected enm: Enum) { + super() + } + isSimpleType() { return false; } + _type(settings: TsTypeSettings) { + // It's a bit hacky, but if this is a top level type, then addDeclaration changes + // our enum type's ID out from under us when it adds the enum to the declaration map, *after* + // the util class is declared. So we name ourselves by our enum's type, not our own ID' + return `${this.enm.toSafeType(settings)}Util` || this.safeId() || "SomeEnumTypeUtils"; + } + toSafeType(settings: TsTypeSettings) { + return `${this.toType(settings)}`; + } + toDeclaration(settings: TsTypeSettings): string { + return `${this.toBlockComment(settings)}${settings.exportInterfaces ? "export " : ""}class ${this._type(settings)} { + ${this.makeValuesMethod(settings)} + ${this.makeToStringValueMethod(settings)} + ${this.makeFromStringValueMethod(settings)} + ${this.makeFromStringValuesMethod(settings)} + }`; + } + makeValuesMethod(settings: TsTypeSettings){ + let enumType = this.enm.toSafeType(settings) + return `static values(): ${enumType}[] { + return [${this.enm.enumValues.map(_ => `${enumType}.${_.identifier}`).join(',')}] + }` + } + makeFromStringValueMethod(settings: TsTypeSettings){ + let enumType = this.enm.toSafeType(settings) + return `static fromStringValue(value: string): ${enumType} { + switch(value.toLowerCase()){ + ${this.enm.enumValues.map(_ => `case "${_.identifier.toLowerCase()}": + return ${enumType + '.' + _.identifier};`).join('\n')} + default: + throw new Error("Unrecognized ${enumType}: " + value); + } + }` + } + makeToStringValueMethod(settings: TsTypeSettings){ + let enumType = this.enm.toSafeType(settings) + return `static toStringValue(enm: ${enumType}): ${enumType} { + switch(enm.toLowerCase()){ + ${this.enm.enumValues.map(_ => `case ${enumType + '.' + _.identifier}: + return "${_.identifier.toLowerCase()}";`).join('\n')} + } + }` + } + makeFromStringValuesMethod(settings: TsTypeSettings){ + let enumType = this.enm.toSafeType(settings) + return `static fromStringValues(values: string[]): ${enumType}[] { + return _.map(values, value => ${this._type(settings)}.fromStringValue(value)); + }` + } + + } export class Array extends TsType { diff --git a/src/index.ts b/src/index.ts index 71c8c3a3..ddea0f75 100644 --- a/src/index.ts +++ b/src/index.ts @@ -185,13 +185,25 @@ class Compiler { // identifiers as EnumOneTwoThree for enum: ["One", "Two", "Three"]. Ugly, but // practical. let path = rule.id || propName || ("Enum" + enumValues.map(_ => _.identifier).join("")); - let retVal: TsType.TsType = new TsType.Enum(enumValues); + + let enm = new TsType.Enum(enumValues); + let retVal: TsType.TsType = enm; // don't add this to the declarations map if this is the top-level type (already declared) // or if it's a reference and we don't want to declare those. - if(!isTop && (!isReference || this.settings.declareReferenced)){ - retVal = this.declareType(retVal, path, path); + if((!isReference || this.settings.declareReferenced)){ + if(!isTop){ + retVal = this.declareType(retVal, path, path); + } else { + retVal.id = path; + } + + if(this.settings.addEnumUtils){ + let utilPath = path + "Utils" + this.declareType(new TsType.EnumUtils(enm), utilPath, utilPath) + } } + return retVal; } else { From 752f60db9ec817aaa4c134274b859d25f685cf02 Mon Sep 17 00:00:00 2001 From: Jonathan Ogilvie Date: Thu, 1 Sep 2016 17:17:37 -0400 Subject: [PATCH 10/27] Adding util class generation for enums --- dist/index.js | 4 +- src/index.ts | 5 +- test/cases/enum.ts | 116 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 122 insertions(+), 3 deletions(-) diff --git a/dist/index.js b/dist/index.js index 65972c8a..32bd76a3 100644 --- a/dist/index.js +++ b/dist/index.js @@ -454,7 +454,9 @@ var Compiler = (function () { // TODO: what to do in the case where the value is an Object? // right now we just pring [Object object] as the literal which is bad if (this.settings.useTypescriptEnums) { - var enumValues = lodash_1.zip(rule.tsEnumNames || [], rule.enum.map(function (_) { return new TsTypes_1.TsType.Literal(_).toType(_this.settings); })) + var enumValues = lodash_1.zip(rule.tsEnumNames || [], + // If we try to create a literal from an object, bad stuff can happen... so we have to toString it + rule.enum.map(function (_) { return new TsTypes_1.TsType.Literal(_).toType(_this.settings).toString(); })) .map(function (_) { return new TsTypes_1.TsType.EnumValue(_); }); // name our anonymous enum, if it doesn't have an ID, by the property name under // which it was declared. Failing both of these things, it'll concat together the diff --git a/src/index.ts b/src/index.ts index ddea0f75..a84c0e2f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -177,7 +177,8 @@ class Compiler { // right now we just pring [Object object] as the literal which is bad if(this.settings.useTypescriptEnums){ var enumValues = zip(rule.tsEnumNames || [], - rule.enum!.map(_ => new TsType.Literal(_).toType(this.settings))) + // If we try to create a literal from an object, bad stuff can happen... so we have to toString it + rule.enum!.map(_ => new TsType.Literal(_).toType(this.settings).toString())) .map(_ => new TsType.EnumValue(_)); // name our anonymous enum, if it doesn't have an ID, by the property name under @@ -197,7 +198,7 @@ class Compiler { } else { retVal.id = path; } - + if(this.settings.addEnumUtils){ let utilPath = path + "Utils" this.declareType(new TsType.EnumUtils(enm), utilPath, utilPath) diff --git a/test/cases/enum.ts b/test/cases/enum.ts index 07f489bd..4bd02bfd 100644 --- a/test/cases/enum.ts +++ b/test/cases/enum.ts @@ -53,6 +53,122 @@ enum Baz { [object Object], [object Object] } +interface Enum { + foo: Foo; + bar: Bar; + baz: Baz; +}` + }, + { + settings: { + useTypescriptEnums: true, + addEnumUtils: true + }, + types: `enum Foo { + a, + b, + c +} +class FooUtil { + static values(): Foo[] { + return [Foo.a, Foo.b, Foo.c] + } + static toStringValue(enm: Foo): Foo { + switch (enm.toLowerCase()) { + case Foo.a: + return "a"; + case Foo.b: + return "b"; + case Foo.c: + return "c"; + } + } + static fromStringValue(value: string): Foo { + switch (value.toLowerCase()) { + case "a": + return Foo.a; + case "b": + return Foo.b; + case "c": + return Foo.c; + default: + throw new Error("Unrecognized Foo: " + value); + } + } + static fromStringValues(values: string[]): Foo[] { + return _.map(values, value => FooUtil.fromStringValue(value)); + } +} +enum Bar { + One = 1, + Two = 2, + Three = 3 +} +class BarUtil { + static values(): Bar[] { + return [Bar.One, Bar.Two, Bar.Three] + } + static toStringValue(enm: Bar): Bar { + switch (enm.toLowerCase()) { + case Bar.One: + return "one"; + case Bar.Two: + return "two"; + case Bar.Three: + return "three"; + } + } + static fromStringValue(value: string): Bar { + switch (value.toLowerCase()) { + case "one": + return Bar.One; + case "two": + return Bar.Two; + case "three": + return Bar.Three; + default: + throw new Error("Unrecognized Bar: " + value); + } + } + static fromStringValues(values: string[]): Bar[] { + return _.map(values, value => BarUtil.fromStringValue(value)); + } +} +enum Baz { + [object Object], + [object Object], + [object Object] +} +class BazUtil { + static values(): Baz[] { + return [Baz.[object Object], Baz.[object Object],Baz.[object Object]] + } + static toStringValue(enm: Baz): Baz { + switch (enm.toLowerCase()) { + case Baz.[object Object]: + return "[object object]"; + case Baz.[object Object]: + return "[object object]"; + case Baz.[object Object]: + return "[object object]"; + } + } + static fromStringValue(value: string): Baz { + switch (value.toLowerCase()) { + case "[object object]": + return Baz.[object Object]; + case "[object object]": + return Baz.[object Object]; + case "[object object]": + return Baz.[object Object]; + default: + throw new Error("Unrecognized Baz: " + value); + } + } + static fromStringValues(values: string[]): Baz[] { + return _.map(values, value => BazUtil.fromStringValue(value)); + } +} interface Enum { foo: Foo; bar: Bar; From f992eea7cdfef63ff9608dd9150254e53c1e9280 Mon Sep 17 00:00:00 2001 From: Jonathan Ogilvie Date: Thu, 1 Sep 2016 17:23:53 -0400 Subject: [PATCH 11/27] Bug --- src/TsTypes.ts | 2 +- test/cases/enum.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/TsTypes.ts b/src/TsTypes.ts index deea22a4..4aa22733 100644 --- a/src/TsTypes.ts +++ b/src/TsTypes.ts @@ -190,7 +190,7 @@ export class EnumUtils extends TsType { } makeToStringValueMethod(settings: TsTypeSettings){ let enumType = this.enm.toSafeType(settings) - return `static toStringValue(enm: ${enumType}): ${enumType} { + return `static toStringValue(enm: ${enumType}): string { switch(enm.toLowerCase()){ ${this.enm.enumValues.map(_ => `case ${enumType + '.' + _.identifier}: return "${_.identifier.toLowerCase()}";`).join('\n')} diff --git a/test/cases/enum.ts b/test/cases/enum.ts index 4bd02bfd..d50af2fe 100644 --- a/test/cases/enum.ts +++ b/test/cases/enum.ts @@ -73,7 +73,7 @@ class FooUtil { static values(): Foo[] { return [Foo.a, Foo.b, Foo.c] } - static toStringValue(enm: Foo): Foo { + static toStringValue(enm: Foo): string { switch (enm.toLowerCase()) { case Foo.a: return "a"; @@ -108,7 +108,7 @@ class BarUtil { static values(): Bar[] { return [Bar.One, Bar.Two, Bar.Three] } - static toStringValue(enm: Bar): Bar { + static toStringValue(enm: Bar): string { switch (enm.toLowerCase()) { case Bar.One: return "one"; @@ -143,7 +143,7 @@ class BazUtil { static values(): Baz[] { return [Baz.[object Object], Baz.[object Object],Baz.[object Object]] } - static toStringValue(enm: Baz): Baz { + static toStringValue(enm: Baz): string { switch (enm.toLowerCase()) { case Baz.[object Object]: return "[object object]"; From 929ee8eb585b373e1cf84ea47ac0964467b68d2e Mon Sep 17 00:00:00 2001 From: Jonathan Ogilvie Date: Thu, 1 Sep 2016 17:24:18 -0400 Subject: [PATCH 12/27] Commit dist --- dist/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dist/index.js b/dist/index.js index 32bd76a3..8c15b9e2 100644 --- a/dist/index.js +++ b/dist/index.js @@ -199,7 +199,7 @@ var TsType; }; EnumUtils.prototype.makeToStringValueMethod = function (settings) { var enumType = this.enm.toSafeType(settings); - return "static toStringValue(enm: " + enumType + "): " + enumType + " {\n switch(enm.toLowerCase()){\n " + this.enm.enumValues.map(function (_) { return ("case " + (enumType + '.' + _.identifier) + ":\n return \"" + _.identifier.toLowerCase() + "\";"); }).join('\n') + "\n }\n }"; + return "static toStringValue(enm: " + enumType + "): string {\n switch(enm.toLowerCase()){\n " + this.enm.enumValues.map(function (_) { return ("case " + (enumType + '.' + _.identifier) + ":\n return \"" + _.identifier.toLowerCase() + "\";"); }).join('\n') + "\n }\n }"; }; EnumUtils.prototype.makeFromStringValuesMethod = function (settings) { var enumType = this.enm.toSafeType(settings); From dc38627e2439ef24bcd03af978f69e49a90cbc3d Mon Sep 17 00:00:00 2001 From: Jonathan Ogilvie Date: Thu, 1 Sep 2016 17:28:09 -0400 Subject: [PATCH 13/27] Fix toStringValue generated method --- dist/index.js | 2 +- src/TsTypes.ts | 2 +- test/cases/enum.ts | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/dist/index.js b/dist/index.js index 8c15b9e2..005d0158 100644 --- a/dist/index.js +++ b/dist/index.js @@ -199,7 +199,7 @@ var TsType; }; EnumUtils.prototype.makeToStringValueMethod = function (settings) { var enumType = this.enm.toSafeType(settings); - return "static toStringValue(enm: " + enumType + "): string {\n switch(enm.toLowerCase()){\n " + this.enm.enumValues.map(function (_) { return ("case " + (enumType + '.' + _.identifier) + ":\n return \"" + _.identifier.toLowerCase() + "\";"); }).join('\n') + "\n }\n }"; + return "static toStringValue(enm: " + enumType + "): string {\n switch(enm){\n " + this.enm.enumValues.map(function (_) { return ("case " + (enumType + '.' + _.identifier) + ":\n return \"" + _.identifier.toLowerCase() + "\";"); }).join('\n') + "\n }\n }"; }; EnumUtils.prototype.makeFromStringValuesMethod = function (settings) { var enumType = this.enm.toSafeType(settings); diff --git a/src/TsTypes.ts b/src/TsTypes.ts index 4aa22733..e6238ee5 100644 --- a/src/TsTypes.ts +++ b/src/TsTypes.ts @@ -191,7 +191,7 @@ export class EnumUtils extends TsType { makeToStringValueMethod(settings: TsTypeSettings){ let enumType = this.enm.toSafeType(settings) return `static toStringValue(enm: ${enumType}): string { - switch(enm.toLowerCase()){ + switch(enm){ ${this.enm.enumValues.map(_ => `case ${enumType + '.' + _.identifier}: return "${_.identifier.toLowerCase()}";`).join('\n')} } diff --git a/test/cases/enum.ts b/test/cases/enum.ts index d50af2fe..585c5096 100644 --- a/test/cases/enum.ts +++ b/test/cases/enum.ts @@ -74,7 +74,7 @@ class FooUtil { return [Foo.a, Foo.b, Foo.c] } static toStringValue(enm: Foo): string { - switch (enm.toLowerCase()) { + switch (enm) { case Foo.a: return "a"; case Foo.b: @@ -109,7 +109,7 @@ class BarUtil { return [Bar.One, Bar.Two, Bar.Three] } static toStringValue(enm: Bar): string { - switch (enm.toLowerCase()) { + switch (enm) { case Bar.One: return "one"; case Bar.Two: @@ -144,7 +144,7 @@ class BazUtil { return [Baz.[object Object], Baz.[object Object],Baz.[object Object]] } static toStringValue(enm: Baz): string { - switch (enm.toLowerCase()) { + switch (enm) { case Baz.[object Object]: return "[object object]"; case Baz.[object Object]: From 80fbb6d3e847abc6f42ea77cfd70e8bfd5334ee7 Mon Sep 17 00:00:00 2001 From: Jonathan Ogilvie Date: Tue, 6 Sep 2016 12:15:30 -0400 Subject: [PATCH 14/27] Lint --- src/TsTypes.ts | 78 +++++++++++++++++------------------- src/index.ts | 106 ++++++++++++++++++++++++------------------------- 2 files changed, 89 insertions(+), 95 deletions(-) diff --git a/src/TsTypes.ts b/src/TsTypes.ts index e6238ee5..07a8f4c9 100644 --- a/src/TsTypes.ts +++ b/src/TsTypes.ts @@ -3,33 +3,32 @@ import { camelCase, upperFirst } from 'lodash' export namespace TsType { export interface TsTypeSettings { - declareSimpleType?: boolean + addEnumUtils?: boolean + declarationDescription?: boolean + // TODO declareProperties?: boolean declareReferenced?: boolean + declareSimpleType?: boolean + exportInterfaces?: boolean + endPropertyWithSemicolon?: boolean + endTypeWithSemicolon?: boolean + propertyDescription?: boolean useFullReferencePathAsName?: boolean - // TODO declareProperties?: boolean useInterfaceDeclaration?: boolean - useTypescriptEnums?: boolean; - exportInterfaces?: boolean; - endTypeWithSemicolon?: boolean - endPropertyWithSemicolon?: boolean - declarationDescription?: boolean - propertyDescription?: boolean, - addEnumUtils?: boolean + useTypescriptEnums?: boolean } export var DEFAULT_SETTINGS: TsTypeSettings = { + addEnumUtils: false, declarationDescription: true, + // declareProperties: false, declareReferenced: true, declareSimpleType: false, endPropertyWithSemicolon: true, endTypeWithSemicolon: true, propertyDescription: true, useFullReferencePathAsName: false, - // declareProperties: false, useInterfaceDeclaration: true, - useTypescriptEnums: false, - exportInterfaces: false, - addEnumUtils: false + useTypescriptEnums: false } export abstract class TsType { @@ -105,20 +104,20 @@ export namespace TsType { } export class EnumValue { - identifier: string; - value: string; + identifier: string + value: string constructor(enumValues: string[]) { - let hasValue = !!enumValues[0]; + let hasValue = !!enumValues[0] // quirky propagation logic - if(hasValue){ - this.identifier = enumValues[0]; - this.value = enumValues[1]; + if (hasValue){ + this.identifier = enumValues[0] + this.value = enumValues[1] } else { - this.identifier = enumValues[1]; + this.identifier = enumValues[1] } - } + } toDeclaration(){ // if there is a value associated with the identifier, declare as identifier=value @@ -132,44 +131,44 @@ export class EnumValue { } export class Enum extends TsType { - constructor(public enumValues: EnumValue[]) { - super() + constructor(public enumValues: EnumValue[]) { + super() } - isSimpleType() { return false; } + isSimpleType() { return false } _type(settings: TsTypeSettings) { - return this.safeId() || "SomeEnumType"; + return this.safeId() || 'SomeEnumType' } toSafeType(settings: TsTypeSettings) { - return `${this.toType(settings)}`; + return `${this.toType(settings)}` } toDeclaration(settings: TsTypeSettings): string { - return `${this.toBlockComment(settings)}${settings.exportInterfaces ? "export " : ""}enum ${this._type(settings)}{ + return `${this.toBlockComment(settings)}export enum ${this._type(settings)}{ ${this.enumValues.map(_ => _.toDeclaration()).join(',\n')} - }`; + }` } } export class EnumUtils extends TsType { - constructor(protected enm: Enum) { - super() + constructor(protected enm: Enum) { + super() } - isSimpleType() { return false; } + isSimpleType() { return false } _type(settings: TsTypeSettings) { // It's a bit hacky, but if this is a top level type, then addDeclaration changes // our enum type's ID out from under us when it adds the enum to the declaration map, *after* // the util class is declared. So we name ourselves by our enum's type, not our own ID' - return `${this.enm.toSafeType(settings)}Util` || this.safeId() || "SomeEnumTypeUtils"; + return `${this.enm.toSafeType(settings)}Util` || this.safeId() || 'SomeEnumTypeUtils' } toSafeType(settings: TsTypeSettings) { - return `${this.toType(settings)}`; + return `${this.toType(settings)}` } toDeclaration(settings: TsTypeSettings): string { - return `${this.toBlockComment(settings)}${settings.exportInterfaces ? "export " : ""}class ${this._type(settings)} { + return `${this.toBlockComment(settings)}export class ${this._type(settings)} { ${this.makeValuesMethod(settings)} ${this.makeToStringValueMethod(settings)} ${this.makeFromStringValueMethod(settings)} ${this.makeFromStringValuesMethod(settings)} - }`; + }` } makeValuesMethod(settings: TsTypeSettings){ let enumType = this.enm.toSafeType(settings) @@ -203,8 +202,6 @@ export class EnumUtils extends TsType { return _.map(values, value => ${this._type(settings)}.fromStringValue(value)); }` } - - } export class Array extends TsType { @@ -262,12 +259,11 @@ export class EnumUtils extends TsType { }).join('\n')} }` : id } - isSimpleType() { return false; } + isSimpleType() { return false } toDeclaration(settings: TsTypeSettings): string { if (settings.useInterfaceDeclaration) - return `${this.toBlockComment(settings)}${settings.exportInterfaces ? "export " : ""}interface ${this.safeId()} ${this._type(settings, true)}`; - else - return this._toDeclaration(`type ${this.safeId()} = ${this._type(settings, true)}`, settings); + return `${this.toBlockComment(settings)}"export interface ${this.safeId()} ${this._type(settings, true)}` + return this._toDeclaration(`type ${this.safeId()} = ${this._type(settings, true)}`, settings) } } diff --git a/src/index.ts b/src/index.ts index a84c0e2f..97980829 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,9 +2,8 @@ import { JSONSchema } from './JSONSchema' import { format } from './pretty-printer' import { TsType } from './TsTypes' import { readFile, readFileSync } from 'fs' -import { dirname, join, parse, resolve, ParsedPath } from 'path' -import { Readable } from 'stream' -import { camelCase, isPlainObject, last, map, merge, uniqBy, upperFirst, zip } from 'lodash' +import { isPlainObject, last, map, merge, uniqBy, zip } from 'lodash' +import { join, parse, ParsedPath, resolve } from 'path' enum RuleType { 'Any', 'TypedArray', 'Enum', 'AllOf', 'AnyOf', 'Reference', 'NamedSchema', 'AnonymousSchema', @@ -22,12 +21,12 @@ class Compiler { } constructor(private schema: JSONSchema.Schema, filePath: string, settings?: TsType.TsTypeSettings) { - let path = resolve(filePath); - this.filePath = parse(path); + let path = resolve(filePath) + this.filePath = parse(path) this.declarations = new Map - this.id = schema.id || schema.title || this.filePath.name || 'Interface1'; - this.settings = Object.assign({}, Compiler.DEFAULT_SETTINGS, settings); - this.declareType(this.toTsType(this.schema, '', true), this.id, this.id); + this.id = schema.id || schema.title || this.filePath.name || 'Interface1' + this.settings = Object.assign({}, Compiler.DEFAULT_SETTINGS, settings) + this.declareType(this.toTsType(this.schema, '', true), this.id, this.id) } toString(): string { @@ -38,10 +37,10 @@ class Compiler { ) } - private settings: TsType.TsTypeSettings; - private id: string; - private declarations: Map; - private filePath: ParsedPath; + private settings: TsType.TsTypeSettings + private id: string + private declarations: Map + private filePath: ParsedPath private isRequired(propertyName: string, schema: JSONSchema.Schema): boolean { return schema.required ? schema.required.indexOf(propertyName) > -1 : false @@ -99,31 +98,30 @@ class Compiler { // only called in case of a $ref type private resolveType(refPath: string, propName: string): TsType.TsType { if (refPath === '#' || refPath === '#/'){ - return TsType.Interface.reference(this.id); - } + return TsType.Interface.reference(this.id) + } if (refPath[0] !== '#'){ - let retVal: TsType.TsType; - let id: string; - let fullPath = resolve(join(this.filePath.dir, refPath)); - let file = readFileSync(fullPath); - let targetType = this.toTsType(JSON.parse(file.toString()), propName, false, true); - if(targetType.id){ - id = targetType.toSafeType(this.settings); + let id: string + let fullPath = resolve(join(this.filePath.dir, refPath)) + let file = readFileSync(fullPath) + let targetType = this.toTsType(JSON.parse(file.toString()), propName, false, true) + if (targetType.id){ + id = targetType.toSafeType(this.settings) } else { - let parsedNewFile = parse(fullPath); - id = parsedNewFile.name; + let parsedNewFile = parse(fullPath) + id = parsedNewFile.name } - if(this.settings.declareReferenced){ - this.declareType(targetType, id, id); - } + if (this.settings.declareReferenced){ + this.declareType(targetType, id, id) + } - return new TsType.Literal(id); + return new TsType.Literal(id) }; - const parts = refPath.slice(2).split('/'); - let ret = this.settings.declareReferenced ? this.declarations.get(parts.join('/')) : undefined; + const parts = refPath.slice(2).split('/') + let ret = this.settings.declareReferenced ? this.declarations.get(parts.join('/')) : undefined if (!ret) { let cur: any = this.schema @@ -138,9 +136,9 @@ class Compiler { } private declareType(type: TsType.TsType, refPath: string, id: string) { - type.id = id; - this.declarations.set(refPath, type); - return type; + type.id = id + this.declarations.set(refPath, type) + return type } private toStringLiteral(a: boolean | number | string | Object): TsType.TsType { @@ -175,42 +173,42 @@ class Compiler { // TODO: what to do in the case where the value is an Object? // right now we just pring [Object object] as the literal which is bad - if(this.settings.useTypescriptEnums){ - var enumValues = zip(rule.tsEnumNames || [], + if (this.settings.useTypescriptEnums){ + var enumValues = zip(rule.tsEnumNames || [], // If we try to create a literal from an object, bad stuff can happen... so we have to toString it rule.enum!.map(_ => new TsType.Literal(_).toType(this.settings).toString())) - .map(_ => new TsType.EnumValue(_)); + .map(_ => new TsType.EnumValue(_)) // name our anonymous enum, if it doesn't have an ID, by the property name under // which it was declared. Failing both of these things, it'll concat together the // identifiers as EnumOneTwoThree for enum: ["One", "Two", "Three"]. Ugly, but // practical. - let path = rule.id || propName || ("Enum" + enumValues.map(_ => _.identifier).join("")); + let path = rule.id || propName || ('Enum' + enumValues.map(_ => _.identifier).join('')) - let enm = new TsType.Enum(enumValues); - let retVal: TsType.TsType = enm; + let enm = new TsType.Enum(enumValues) + let retVal: TsType.TsType = enm // don't add this to the declarations map if this is the top-level type (already declared) // or if it's a reference and we don't want to declare those. - if((!isReference || this.settings.declareReferenced)){ - if(!isTop){ - retVal = this.declareType(retVal, path, path); + if ((!isReference || this.settings.declareReferenced)){ + if (!isTop){ + retVal = this.declareType(retVal, path, path) } else { - retVal.id = path; + retVal.id = path } - if(this.settings.addEnumUtils){ - let utilPath = path + "Utils" + if (this.settings.addEnumUtils){ + let utilPath = path + 'Utils' this.declareType(new TsType.EnumUtils(enm), utilPath, utilPath) } } - return retVal; - + return retVal + } else { return new TsType.Union(uniqBy( rule.enum!.map(_ => this.toStringLiteral(_)) - , _ => _.toType(this.settings))); + , _ => _.toType(this.settings))) } case RuleType.Any: return new TsType.Any case RuleType.Literal: return new TsType.Literal(rule) @@ -226,16 +224,16 @@ class Compiler { case RuleType.AnyOf: return new TsType.Union(rule.anyOf!.map(_ => this.toTsType(_))) case RuleType.Reference: - return this.resolveType(rule.$ref!, propName!); + return this.resolveType(rule.$ref!, propName!) } throw new Error('Unknown rule:' + rule.toString()) - } + } private toTsType (rule: JSONSchema.Schema, propName?: string, isTop: boolean = false, isReference: boolean = false): TsType.TsType { - let type = this.createTsType(rule, propName, isTop, isReference); - type.id = type.id || rule.id || rule.title; - type.description = type.description || rule.description; - return type; + let type = this.createTsType(rule, propName, isTop, isReference) + type.id = type.id || rule.id || rule.title + type.description = type.description || rule.description + return type } private toTsDeclaration(schema: JSONSchema.Schema): TsType.TsType { let copy = merge({}, Compiler.DEFAULT_SCHEMA, schema) @@ -245,7 +243,7 @@ class Compiler { return { name: k, required: this.isRequired(k, copy), - type: this.toTsType(v, k), + type: this.toTsType(v, k) } }) if (props.length === 0 && !('additionalProperties' in schema)) { From ce84a4bdcf23de2529e17b2c6e9751dcda941b8e Mon Sep 17 00:00:00 2001 From: Jonathan Ogilvie Date: Tue, 6 Sep 2016 12:56:58 -0400 Subject: [PATCH 15/27] Bad quote! Bad! --- dist/index.d.ts | 14 ++++++------- dist/index.js | 33 ++++++++++++++---------------- src/TsTypes.ts | 6 +++--- test/cases/additionalProperties.ts | 2 +- test/cases/allOf.ts | 6 +++--- test/cases/anyOf.ts | 8 ++++---- test/cases/array-of-type.ts | 2 +- test/cases/basics.ts | 19 ++--------------- test/cases/enum.ts | 24 +++++++++++----------- test/cases/json-schema.ts | 4 ++-- test/cases/not-extendable.ts | 2 +- test/cases/ref.ts | 6 +++--- test/cases/root-anyOf.ts | 6 +++--- test/cases/unnamedSchema.ts | 2 +- test/cases/with-description.ts | 2 +- 15 files changed, 59 insertions(+), 77 deletions(-) diff --git a/dist/index.d.ts b/dist/index.d.ts index ad75abc6..74b7f5ba 100644 --- a/dist/index.d.ts +++ b/dist/index.d.ts @@ -57,17 +57,17 @@ export namespace JSONSchema { export namespace TsType { interface TsTypeSettings { - declareSimpleType?: boolean; + addEnumUtils?: boolean; + declarationDescription?: boolean; declareReferenced?: boolean; - useFullReferencePathAsName?: boolean; - useInterfaceDeclaration?: boolean; - useTypescriptEnums?: boolean; + declareSimpleType?: boolean; exportInterfaces?: boolean; - endTypeWithSemicolon?: boolean; endPropertyWithSemicolon?: boolean; - declarationDescription?: boolean; + endTypeWithSemicolon?: boolean; propertyDescription?: boolean; - addEnumUtils?: boolean; + useFullReferencePathAsName?: boolean; + useInterfaceDeclaration?: boolean; + useTypescriptEnums?: boolean; } var DEFAULT_SETTINGS: TsTypeSettings; abstract class TsType { diff --git a/dist/index.js b/dist/index.js index 005d0158..b890ca66 100644 --- a/dist/index.js +++ b/dist/index.js @@ -9,18 +9,17 @@ var lodash_1 = require('lodash'); var TsType; (function (TsType_1) { TsType_1.DEFAULT_SETTINGS = { + addEnumUtils: false, declarationDescription: true, + // declareProperties: false, declareReferenced: true, declareSimpleType: false, endPropertyWithSemicolon: true, endTypeWithSemicolon: true, propertyDescription: true, useFullReferencePathAsName: false, - // declareProperties: false, useInterfaceDeclaration: true, - useTypescriptEnums: false, - exportInterfaces: false, - addEnumUtils: false + useTypescriptEnums: false }; var TsType = (function () { function TsType() { @@ -159,13 +158,13 @@ var TsType; } Enum.prototype.isSimpleType = function () { return false; }; Enum.prototype._type = function (settings) { - return this.safeId() || "SomeEnumType"; + return this.safeId() || 'SomeEnumType'; }; Enum.prototype.toSafeType = function (settings) { return "" + this.toType(settings); }; Enum.prototype.toDeclaration = function (settings) { - return "" + this.toBlockComment(settings) + (settings.exportInterfaces ? "export " : "") + "enum " + this._type(settings) + "{\n " + this.enumValues.map(function (_) { return _.toDeclaration(); }).join(',\n') + "\n }"; + return this.toBlockComment(settings) + "export enum " + this._type(settings) + "{\n " + this.enumValues.map(function (_) { return _.toDeclaration(); }).join(',\n') + "\n }"; }; return Enum; }(TsType)); @@ -181,13 +180,13 @@ var TsType; // It's a bit hacky, but if this is a top level type, then addDeclaration changes // our enum type's ID out from under us when it adds the enum to the declaration map, *after* // the util class is declared. So we name ourselves by our enum's type, not our own ID' - return this.enm.toSafeType(settings) + "Util" || this.safeId() || "SomeEnumTypeUtils"; + return this.enm.toSafeType(settings) + "Util" || this.safeId() || 'SomeEnumTypeUtils'; }; EnumUtils.prototype.toSafeType = function (settings) { return "" + this.toType(settings); }; EnumUtils.prototype.toDeclaration = function (settings) { - return "" + this.toBlockComment(settings) + (settings.exportInterfaces ? "export " : "") + "class " + this._type(settings) + " {\n " + this.makeValuesMethod(settings) + "\n " + this.makeToStringValueMethod(settings) + "\n " + this.makeFromStringValueMethod(settings) + "\n " + this.makeFromStringValuesMethod(settings) + "\n }"; + return this.toBlockComment(settings) + "export class " + this._type(settings) + " {\n " + this.makeValuesMethod(settings) + "\n " + this.makeToStringValueMethod(settings) + "\n " + this.makeFromStringValueMethod(settings) + "\n " + this.makeFromStringValuesMethod(settings) + "\n }"; }; EnumUtils.prototype.makeValuesMethod = function (settings) { var enumType = this.enm.toSafeType(settings); @@ -268,7 +267,7 @@ var TsType; if (declaration === void 0) { declaration = false; } var id = this.safeId(); return declaration || !id ? "{\n " + this.props.map(function (_) { - var decl = _.name; + var decl = ' ' + _.name; if (!_.required) decl += '?'; decl += ': ' + _.type.toType(settings); @@ -277,14 +276,13 @@ var TsType; if (settings.propertyDescription && _.type.description) decl += ' // ' + _.type.description; return decl; - }).join('\n') + "\n }" : id; + }).join('\n') + "\n}" : id; }; Interface.prototype.isSimpleType = function () { return false; }; Interface.prototype.toDeclaration = function (settings) { if (settings.useInterfaceDeclaration) - return "" + this.toBlockComment(settings) + (settings.exportInterfaces ? "export " : "") + "interface " + this.safeId() + " " + this._type(settings, true); - else - return this._toDeclaration("type " + this.safeId() + " = " + this._type(settings, true), settings); + return this.toBlockComment(settings) + "export interface " + this.safeId() + " " + this._type(settings, true); + return this._toDeclaration("type " + this.safeId() + " = " + this._type(settings, true), settings); }; return Interface; }(TsType)); @@ -296,8 +294,8 @@ var TsType; var pretty_printer_1 = require('./pretty-printer'); var TsTypes_1 = require('./TsTypes'); var fs_1 = require('fs'); -var path_1 = require('path'); var lodash_1 = require('lodash'); +var path_1 = require('path'); var RuleType; (function (RuleType) { RuleType[RuleType['Any'] = 0] = 'Any'; @@ -388,7 +386,6 @@ var Compiler = (function () { return TsTypes_1.TsType.Interface.reference(this.id); } if (refPath[0] !== '#') { - var retVal = void 0; var id = void 0; var fullPath = path_1.resolve(path_1.join(this.filePath.dir, refPath)); var file = fs_1.readFileSync(fullPath); @@ -462,7 +459,7 @@ var Compiler = (function () { // which it was declared. Failing both of these things, it'll concat together the // identifiers as EnumOneTwoThree for enum: ["One", "Two", "Three"]. Ugly, but // practical. - var path = rule.id || propName || ("Enum" + enumValues.map(function (_) { return _.identifier; }).join("")); + var path = rule.id || propName || ('Enum' + enumValues.map(function (_) { return _.identifier; }).join('')); var enm = new TsTypes_1.TsType.Enum(enumValues); var retVal = enm; // don't add this to the declarations map if this is the top-level type (already declared) @@ -475,7 +472,7 @@ var Compiler = (function () { retVal.id = path; } if (this.settings.addEnumUtils) { - var utilPath = path + "Utils"; + var utilPath = path + 'Utils'; this.declareType(new TsTypes_1.TsType.EnumUtils(enm), utilPath, utilPath); } } @@ -517,7 +514,7 @@ var Compiler = (function () { return { name: k, required: _this.isRequired(k, copy), - type: _this.toTsType(v, k), + type: _this.toTsType(v, k) }; }); if (props.length === 0 && !('additionalProperties' in schema)) { diff --git a/src/TsTypes.ts b/src/TsTypes.ts index 07a8f4c9..3a3c6a74 100644 --- a/src/TsTypes.ts +++ b/src/TsTypes.ts @@ -247,7 +247,7 @@ export class EnumUtils extends TsType { let id = this.safeId() return declaration || !id ? `{ ${this.props.map(_ => { - let decl = _.name + let decl = ' ' + _.name if (!_.required) decl += '?' decl += ': ' + _.type.toType(settings) @@ -257,12 +257,12 @@ export class EnumUtils extends TsType { decl += ' // ' + _.type.description return decl }).join('\n')} - }` : id +}` : id } isSimpleType() { return false } toDeclaration(settings: TsTypeSettings): string { if (settings.useInterfaceDeclaration) - return `${this.toBlockComment(settings)}"export interface ${this.safeId()} ${this._type(settings, true)}` + return `${this.toBlockComment(settings)}export interface ${this.safeId()} ${this._type(settings, true)}` return this._toDeclaration(`type ${this.safeId()} = ${this._type(settings, true)}`, settings) } } diff --git a/test/cases/additionalProperties.ts b/test/cases/additionalProperties.ts index 69d921d2..f5c7a51c 100644 --- a/test/cases/additionalProperties.ts +++ b/test/cases/additionalProperties.ts @@ -11,7 +11,7 @@ export var schema = { } } -export var types = `interface AdditionalProperties { +export var types = `export interface AdditionalProperties { foo?: string; [k: string]: number; }` diff --git a/test/cases/allOf.ts b/test/cases/allOf.ts index 0ed725d9..6e8c2ff3 100644 --- a/test/cases/allOf.ts +++ b/test/cases/allOf.ts @@ -31,13 +31,13 @@ export var schema = { "additionalProperties": false } -export var types = `interface Foo { +export var types = `export interface Foo { a: string; b: number; } -interface Bar { +export interface Bar { a: string; } -interface AllOf { +export interface AllOf { foo: Foo & Bar; }` diff --git a/test/cases/anyOf.ts b/test/cases/anyOf.ts index 568b3269..0037817d 100644 --- a/test/cases/anyOf.ts +++ b/test/cases/anyOf.ts @@ -36,18 +36,18 @@ export var schema = { } -export var types = `interface Foo { +export var types = `export interface Foo { a: string; b?: number; } -interface Bar { +export interface Bar { a?: "a" | "b" | "c"; [k: string]: any; } -interface Baz { +export interface Baz { baz?: Bar; [k: string]: any; } -interface AnyOf { +export interface AnyOf { foo: Foo | Bar | Baz; }` diff --git a/test/cases/array-of-type.ts b/test/cases/array-of-type.ts index f19e4499..94ad7d91 100644 --- a/test/cases/array-of-type.ts +++ b/test/cases/array-of-type.ts @@ -11,7 +11,7 @@ export var schema = { } } -export var types = `interface ArrayOfType { +export var types = `export interface ArrayOfType { foo?: string[]; [k: string]: any; }` diff --git a/test/cases/basics.ts b/test/cases/basics.ts index 6447008f..cd034312 100644 --- a/test/cases/basics.ts +++ b/test/cases/basics.ts @@ -31,22 +31,7 @@ export var configurations = [ settings: { useInterfaceDeclaration: true, }, - types: `interface ExampleSchema { - firstName: string; - lastName: string; - age?: number; // Age in years - height?: number; - favoriteFoods?: any[]; - likesDogs?: boolean; - [k: string]: any; -}` - }, - { - settings: { - useInterfaceDeclaration: true, - exportInterfaces: true - }, - types: `export interface ExampleSchema { + types: `export interface ExampleSchema { firstName: string; lastName: string; age?: number; // Age in years @@ -60,7 +45,7 @@ export var configurations = [ settings: { propertyDescription: false }, - types: `interface ExampleSchema { + types: `export interface ExampleSchema { firstName: string; lastName: string; age?: number; diff --git a/test/cases/enum.ts b/test/cases/enum.ts index 585c5096..0b22886d 100644 --- a/test/cases/enum.ts +++ b/test/cases/enum.ts @@ -26,7 +26,7 @@ export var configurations = [ settings: { useTypescriptEnums: false }, - types: `interface Enum { + types: `export interface Enum { foo: "a" | "b" | "c"; bar: number; baz: { @@ -38,22 +38,22 @@ export var configurations = [ settings: { useTypescriptEnums: true }, - types: `enum Foo { + types: `export enum Foo { a, b, c } -enum Bar { +export enum Bar { One = 1, Two = 2, Three = 3 } -enum Baz { +export enum Baz { [object Object], [object Object], [object Object] } -interface Enum { +export interface Enum { foo: Foo; bar: Bar; baz: Baz; @@ -64,12 +64,12 @@ interface Enum { useTypescriptEnums: true, addEnumUtils: true }, - types: `enum Foo { + types: `export enum Foo { a, b, c } -class FooUtil { +export class FooUtil { static values(): Foo[] { return [Foo.a, Foo.b, Foo.c] } @@ -99,12 +99,12 @@ class FooUtil { return _.map(values, value => FooUtil.fromStringValue(value)); } } -enum Bar { +export enum Bar { One = 1, Two = 2, Three = 3 } -class BarUtil { +export class BarUtil { static values(): Bar[] { return [Bar.One, Bar.Two, Bar.Three] } @@ -134,12 +134,12 @@ class BarUtil { return _.map(values, value => BarUtil.fromStringValue(value)); } } -enum Baz { +export enum Baz { [object Object], [object Object], [object Object] } -class BazUtil { +export class BazUtil { static values(): Baz[] { return [Baz.[object Object], Baz.[object Object],Baz.[object Object]] } @@ -169,7 +169,7 @@ class BazUtil { return _.map(values, value => BazUtil.fromStringValue(value)); } } -interface Enum { +export interface Enum { foo: Foo; bar: Bar; baz: Baz; diff --git a/test/cases/json-schema.ts b/test/cases/json-schema.ts index 5307e1d7..44a0c81f 100644 --- a/test/cases/json-schema.ts +++ b/test/cases/json-schema.ts @@ -160,7 +160,7 @@ type SchemaArray = HttpJsonSchemaOrgDraft04Schema[]; type StringArray = string[]; type SimpleTypes = "array" | "boolean" | "integer" | "null" | "number" | "object" | "string"; /** Core schema meta-schema */ -interface HttpJsonSchemaOrgDraft04Schema { +export interface HttpJsonSchemaOrgDraft04Schema { id?: string; $schema?: string; title?: string; @@ -210,7 +210,7 @@ interface HttpJsonSchemaOrgDraft04Schema { }, types:`type SimpleTypes = "array" | "boolean" | "integer" | "null" | "number" | "object" | "string"; /** Core schema meta-schema */ -interface HttpJsonSchemaOrgDraft04Schema { +export interface HttpJsonSchemaOrgDraft04Schema { id?: string; $schema?: string; title?: string; diff --git a/test/cases/not-extendable.ts b/test/cases/not-extendable.ts index c4d03039..8faa6f8d 100644 --- a/test/cases/not-extendable.ts +++ b/test/cases/not-extendable.ts @@ -18,7 +18,7 @@ export var schema = { "additionalProperties": false } -export var types = `interface ExampleSchema { +export var types = `export interface ExampleSchema { firstName: string; lastName: string; age?: number; // Age in years diff --git a/test/cases/ref.ts b/test/cases/ref.ts index a7f3a433..68d3b0e7 100644 --- a/test/cases/ref.ts +++ b/test/cases/ref.ts @@ -16,7 +16,7 @@ export var configurations = [ settings: { declareReferenced: true }, - types: `interface ExampleSchema { + types: `export interface ExampleSchema { firstName: string; lastName: string; age?: number; // Age in years @@ -25,7 +25,7 @@ export var configurations = [ likesDogs?: boolean; [k: string]: any; } -interface Referencing { +export interface Referencing { foo: ExampleSchema; }` }, @@ -33,7 +33,7 @@ interface Referencing { settings: { declareReferenced: false }, - types: `interface Referencing { + types: `export interface Referencing { foo: ExampleSchema; }` } diff --git a/test/cases/root-anyOf.ts b/test/cases/root-anyOf.ts index 28e0de99..ab7d4ce6 100644 --- a/test/cases/root-anyOf.ts +++ b/test/cases/root-anyOf.ts @@ -27,15 +27,15 @@ export var schema = { } } -export var types = `interface Foo { +export var types = `export interface Foo { a: string; b?: number; } -interface Bar { +export interface Bar { a?: "a" | "b" | "c"; [k: string]: any; } -interface Baz { +export interface Baz { baz?: Bar; [k: string]: any; } diff --git a/test/cases/unnamedSchema.ts b/test/cases/unnamedSchema.ts index 5c170640..654b7fb8 100644 --- a/test/cases/unnamedSchema.ts +++ b/test/cases/unnamedSchema.ts @@ -9,6 +9,6 @@ export var schema = { "additionalProperties": false } -export var types = `interface UnnamedSchema { +export var types = `export interface UnnamedSchema { foo: string; }` diff --git a/test/cases/with-description.ts b/test/cases/with-description.ts index 832aa59b..d7afc489 100644 --- a/test/cases/with-description.ts +++ b/test/cases/with-description.ts @@ -19,7 +19,7 @@ export var schema = { } export var types = `/** My cool schema */ -interface ExampleSchema { +export interface ExampleSchema { firstName: string; lastName: string; age?: number; // Age in years From 4174c1f48d1e1f80ec301d9d45d691d7380cc32e Mon Sep 17 00:00:00 2001 From: Jonathan Ogilvie Date: Tue, 6 Sep 2016 16:42:42 -0400 Subject: [PATCH 16/27] Adding enum validation/error case tests --- dist/index.js | 40 +++++++++++++++++++++++-- src/index.ts | 49 ++++++++++++++++++++++++++++-- test/cases/enum.ts | 55 ++-------------------------------- test/cases/enumValidation.1.ts | 22 ++++++++++++++ test/cases/enumValidation.2.ts | 22 ++++++++++++++ test/cases/enumValidation.3.ts | 21 +++++++++++++ test/cases/enumValidation.4.ts | 22 ++++++++++++++ test/cases/enumValidation.ts | 21 +++++++++++++ test/test.ts | 12 ++++++-- 9 files changed, 205 insertions(+), 59 deletions(-) create mode 100644 test/cases/enumValidation.1.ts create mode 100644 test/cases/enumValidation.2.ts create mode 100644 test/cases/enumValidation.3.ts create mode 100644 test/cases/enumValidation.4.ts create mode 100644 test/cases/enumValidation.ts diff --git a/dist/index.js b/dist/index.js index b890ca66..3f6f06db 100644 --- a/dist/index.js +++ b/dist/index.js @@ -448,9 +448,8 @@ var Compiler = (function () { // TODO: honor the schema's "type" on the enum. if string, // skip all the zipping mess; if int, either require the tsEnumNames // or generate literals for the values - // TODO: what to do in the case where the value is an Object? - // right now we just pring [Object object] as the literal which is bad if (this.settings.useTypescriptEnums) { + this.validateEnumMembers(rule); var enumValues = lodash_1.zip(rule.tsEnumNames || [], // If we try to create a literal from an object, bad stuff can happen... so we have to toString it rule.enum.map(function (_) { return new TsTypes_1.TsType.Literal(_).toType(_this.settings).toString(); })) @@ -499,6 +498,43 @@ var Compiler = (function () { } throw new Error('Unknown rule:' + rule.toString()); }; + Compiler.prototype.validateEnumMembers = function (rule) { + if (!rule.type) + rule.type = 'string'; + var isDeclaredStringEnum = rule.type === 'string'; + var isDeclaredIntegerEnum = rule.type === 'integer'; + if (!isDeclaredStringEnum && !isDeclaredIntegerEnum) { + throw TypeError('Enum type must be string or integer when useTypescriptEnums=true; default is string if undefined'); + } + if (rule.enum.some(function (_) { return _ instanceof Object; })) { + throw TypeError('Enum members must be a list of strings or a list of integers when useTypescriptEnums=true; instead, found an Object'); + } + var isActuallyStringEnum = rule.enum.every(function (_) { return typeof (_) === 'string'; }); + var isActuallyIntegerEnum = rule.enum.every(function (_) { return typeof (_) === 'number'; }); + var isIntegerEnumWithValidStringValues = isActuallyIntegerEnum + && rule.tsEnumNames + && rule.tsEnumNames.length === rule.enum.length + && rule.tsEnumNames.every(function (_) { return typeof (_) === 'string'; }); + if (isDeclaredStringEnum && !isActuallyStringEnum) { + throw TypeError('Enum was declared as a string type but found at least one non-string member'); + } + if (isDeclaredIntegerEnum && !isIntegerEnumWithValidStringValues) { + if (!isActuallyIntegerEnum) { + throw TypeError('Enum was declared as an integer type, but found at least one non-integer member'); + } + if (!rule.tsEnumNames) { + throw TypeError('Property tsEnumValues is required when enum is declared as an integer type'); + } + if (rule.tsEnumNames.length !== rule.enum.length) { + throw TypeError('Property enum and property tsEnumValues must be the same length'); + } + throw TypeError('Enum was declared as an integer type, but found at least one non-string tsEnumValue'); + } + // I don't think we should ever hit this case. + if (!isActuallyStringEnum && !isIntegerEnumWithValidStringValues) { + throw TypeError('Enum members must be a list of strings or a list of integers (with corresponding tsEnumValues) when useTypescriptEnums=true'); + } + }; Compiler.prototype.toTsType = function (rule, propName, isTop, isReference) { if (isTop === void 0) { isTop = false; } if (isReference === void 0) { isReference = false; } diff --git a/src/index.ts b/src/index.ts index 97980829..304492c6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -171,9 +171,9 @@ class Compiler { // skip all the zipping mess; if int, either require the tsEnumNames // or generate literals for the values - // TODO: what to do in the case where the value is an Object? - // right now we just pring [Object object] as the literal which is bad if (this.settings.useTypescriptEnums){ + this.validateEnumMembers(rule) + var enumValues = zip(rule.tsEnumNames || [], // If we try to create a literal from an object, bad stuff can happen... so we have to toString it rule.enum!.map(_ => new TsType.Literal(_).toType(this.settings).toString())) @@ -229,6 +229,51 @@ class Compiler { throw new Error('Unknown rule:' + rule.toString()) } + private validateEnumMembers(rule: JSONSchema.Schema){ + if (!rule.type) rule.type = 'string' + + let isDeclaredStringEnum = rule.type === 'string' + let isDeclaredIntegerEnum = rule.type === 'integer' + + if (!isDeclaredStringEnum && !isDeclaredIntegerEnum){ + throw TypeError('Enum type must be string or integer when useTypescriptEnums=true; default is string if undefined') + } + + if (rule.enum!.some(_ => _ instanceof Object)){ + throw TypeError('Enum members must be a list of strings or a list of integers when useTypescriptEnums=true; instead, found an Object') + } + + let isActuallyStringEnum = rule.enum!.every(_ => typeof(_) === 'string') + let isActuallyIntegerEnum = rule.enum!.every(_ => typeof(_) === 'number') + let isIntegerEnumWithValidStringValues = isActuallyIntegerEnum + && rule.tsEnumNames + && rule.tsEnumNames.length === rule.enum!.length + && rule.tsEnumNames!.every(_ => typeof(_) === 'string') + + if (isDeclaredStringEnum && !isActuallyStringEnum){ + throw TypeError('Enum was declared as a string type but found at least one non-string member') + } + + if (isDeclaredIntegerEnum && !isIntegerEnumWithValidStringValues){ + if (!isActuallyIntegerEnum){ + throw TypeError('Enum was declared as an integer type, but found at least one non-integer member') + } + if (!rule.tsEnumNames){ + throw TypeError('Property tsEnumValues is required when enum is declared as an integer type') + } + if (rule.tsEnumNames.length !== rule.enum!.length){ + throw TypeError('Property enum and property tsEnumValues must be the same length') + } + + throw TypeError('Enum was declared as an integer type, but found at least one non-string tsEnumValue') + } + + // I don't think we should ever hit this case. + if (!isActuallyStringEnum && !isIntegerEnumWithValidStringValues){ + throw TypeError('Enum members must be a list of strings or a list of integers (with corresponding tsEnumValues) when useTypescriptEnums=true') + } + } + private toTsType (rule: JSONSchema.Schema, propName?: string, isTop: boolean = false, isReference: boolean = false): TsType.TsType { let type = this.createTsType(rule, propName, isTop, isReference) type.id = type.id || rule.id || rule.title diff --git a/test/cases/enum.ts b/test/cases/enum.ts index 0b22886d..6c66b803 100644 --- a/test/cases/enum.ts +++ b/test/cases/enum.ts @@ -6,18 +6,12 @@ export var schema = { "enum": ["a", "b", "c"] }, "bar": { + "type": "integer", "enum": [1, 2, 3], "tsEnumNames": ["One","Two","Three"] - }, - "baz": { - "enum": [ - { "a": 1 }, - { "a": 2 }, - { "a": 3 } - ] } }, - "required": ["foo", "bar", "baz"], + "required": ["foo", "bar"], "additionalProperties": false } @@ -29,9 +23,6 @@ export var configurations = [ types: `export interface Enum { foo: "a" | "b" | "c"; bar: number; - baz: { - a: number; - }; }` }, { @@ -48,15 +39,9 @@ export enum Bar { Two = 2, Three = 3 } -export enum Baz { - [object Object], - [object Object], - [object Object] -} export interface Enum { foo: Foo; bar: Bar; - baz: Baz; }` }, { @@ -134,45 +119,9 @@ export class BarUtil { return _.map(values, value => BarUtil.fromStringValue(value)); } } -export enum Baz { - [object Object], - [object Object], - [object Object] -} -export class BazUtil { - static values(): Baz[] { - return [Baz.[object Object], Baz.[object Object],Baz.[object Object]] - } - static toStringValue(enm: Baz): string { - switch (enm) { - case Baz.[object Object]: - return "[object object]"; - case Baz.[object Object]: - return "[object object]"; - case Baz.[object Object]: - return "[object object]"; - } - } - static fromStringValue(value: string): Baz { - switch (value.toLowerCase()) { - case "[object object]": - return Baz.[object Object]; - case "[object object]": - return Baz.[object Object]; - case "[object object]": - return Baz.[object Object]; - default: - throw new Error("Unrecognized Baz: " + value); - } - } - static fromStringValues(values: string[]): Baz[] { - return _.map(values, value => BazUtil.fromStringValue(value)); - } -} export interface Enum { foo: Foo; bar: Bar; - baz: Baz; }` } ] diff --git a/test/cases/enumValidation.1.ts b/test/cases/enumValidation.1.ts new file mode 100644 index 00000000..e7894c97 --- /dev/null +++ b/test/cases/enumValidation.1.ts @@ -0,0 +1,22 @@ +export var schema = { + "title": "Enum", + "type": "object", + "properties": { + "bar": { + "type": "string", + "enum": ["foo", 2, 3], + "tsEnumNames": ["One","Two","Three"] + } + }, + "required": ["bar"], + "additionalProperties": false +} + +export var settings = { + useTypescriptEnums: true +} + +export var error = { + type: TypeError, + message: 'Enum was declared as a string type but found at least one non-string member' +} diff --git a/test/cases/enumValidation.2.ts b/test/cases/enumValidation.2.ts new file mode 100644 index 00000000..22584ab2 --- /dev/null +++ b/test/cases/enumValidation.2.ts @@ -0,0 +1,22 @@ +export var schema = { + "title": "Enum", + "type": "object", + "properties": { + "bar": { + "type": "integer", + "enum": [1, 2, 3], + "tsEnumNames": ["One",2,"Three"] + } + }, + "required": ["bar"], + "additionalProperties": false +} + +export var settings = { + useTypescriptEnums: true +} + +export var error = { + type: TypeError, + message: 'Enum was declared as an integer type, but found at least one non-string tsEnumValue' +} diff --git a/test/cases/enumValidation.3.ts b/test/cases/enumValidation.3.ts new file mode 100644 index 00000000..244bb978 --- /dev/null +++ b/test/cases/enumValidation.3.ts @@ -0,0 +1,21 @@ +export var schema = { + "title": "Enum", + "type": "object", + "properties": { + "bar": { + "type": "integer", + "enum": [1, 2, 3] + } + }, + "required": ["bar"], + "additionalProperties": false +} + +export var settings = { + useTypescriptEnums: true +} + +export var error = { + type: TypeError, + message: 'Property tsEnumValues is required when enum is declared as an integer type' +} diff --git a/test/cases/enumValidation.4.ts b/test/cases/enumValidation.4.ts new file mode 100644 index 00000000..3e88b03d --- /dev/null +++ b/test/cases/enumValidation.4.ts @@ -0,0 +1,22 @@ +export var schema = { + "title": "Enum", + "type": "object", + "properties": { + "bar": { + "type": "integer", + "enum": [1, 2, 3], + "tsEnumNames": ["One","Three"] + } + }, + "required": ["bar"], + "additionalProperties": false +} + +export var settings = { + useTypescriptEnums: true +} + +export var error = { + type: TypeError, + message: 'Property enum and property tsEnumValues must be the same length' +} diff --git a/test/cases/enumValidation.ts b/test/cases/enumValidation.ts new file mode 100644 index 00000000..a3223d00 --- /dev/null +++ b/test/cases/enumValidation.ts @@ -0,0 +1,21 @@ +export var schema = { + "title": "Enum", + "type": "object", + "properties": { + "foo": { + "type": "integer", + "enum": ["a", "b", "c"] + } + }, + "required": ["foo"], + "additionalProperties": false +} + +export var settings = { + useTypescriptEnums: true +} + +export var error = { + type: TypeError, + message: 'Enum was declared as an integer type, but found at least one non-integer member' +} diff --git a/test/test.ts b/test/test.ts index 95ff7e01..49bf82bb 100644 --- a/test/test.ts +++ b/test/test.ts @@ -21,14 +21,22 @@ modules.forEach((exports, name) => { describe(name, function() { exports.configurations.forEach((cfg: any) => { it(JSON.stringify(cfg.settings), () => { - expect(compile(exports.schema, name, cfg.settings)).to.be.equal(cfg.types) + if (cfg.error){ + expect(() => compile(cfg.schema, name, cfg.settings) ).to.throw(cfg.error.type, cfg.error.message) + } else { + expect(compile(exports.schema, name, cfg.settings)).to.be.equal(cfg.types) + } }) }) }) } else { describe(name, function() { it('default settings', () => { - expect(compile(exports.schema, name, exports.settings)).to.be.equal(exports.types) + if (exports.error){ + expect(() => compile(exports.schema, name, exports.settings) ).to.throw(exports.error.type, exports.error.message) + } else { + expect(compile(exports.schema, name, exports.settings)).to.be.equal(exports.types) + } }) }) } From b27d7fe62358755608c2d152ae51f5b64c88e3dd Mon Sep 17 00:00:00 2001 From: Jonathan Ogilvie Date: Tue, 6 Sep 2016 16:58:45 -0400 Subject: [PATCH 17/27] Add ability to generate const enums --- dist/index.d.ts | 1 + dist/index.js | 3 ++- src/TsTypes.ts | 4 +++- test/cases/enum.ts | 22 +++++++++++++++++++++- 4 files changed, 27 insertions(+), 3 deletions(-) diff --git a/dist/index.d.ts b/dist/index.d.ts index 74b7f5ba..d6c02ec3 100644 --- a/dist/index.d.ts +++ b/dist/index.d.ts @@ -65,6 +65,7 @@ export namespace TsType { endPropertyWithSemicolon?: boolean; endTypeWithSemicolon?: boolean; propertyDescription?: boolean; + useConstEnums?: boolean; useFullReferencePathAsName?: boolean; useInterfaceDeclaration?: boolean; useTypescriptEnums?: boolean; diff --git a/dist/index.js b/dist/index.js index 3f6f06db..0418f2f8 100644 --- a/dist/index.js +++ b/dist/index.js @@ -17,6 +17,7 @@ var TsType; endPropertyWithSemicolon: true, endTypeWithSemicolon: true, propertyDescription: true, + useConstEnums: false, useFullReferencePathAsName: false, useInterfaceDeclaration: true, useTypescriptEnums: false @@ -164,7 +165,7 @@ var TsType; return "" + this.toType(settings); }; Enum.prototype.toDeclaration = function (settings) { - return this.toBlockComment(settings) + "export enum " + this._type(settings) + "{\n " + this.enumValues.map(function (_) { return _.toDeclaration(); }).join(',\n') + "\n }"; + return this.toBlockComment(settings) + "export " + (settings.useConstEnums ? "const " : "") + "enum " + this._type(settings) + "{\n " + this.enumValues.map(function (_) { return _.toDeclaration(); }).join(',\n') + "\n }"; }; return Enum; }(TsType)); diff --git a/src/TsTypes.ts b/src/TsTypes.ts index 3a3c6a74..b97c5542 100644 --- a/src/TsTypes.ts +++ b/src/TsTypes.ts @@ -12,6 +12,7 @@ export namespace TsType { endPropertyWithSemicolon?: boolean endTypeWithSemicolon?: boolean propertyDescription?: boolean + useConstEnums?: boolean useFullReferencePathAsName?: boolean useInterfaceDeclaration?: boolean useTypescriptEnums?: boolean @@ -26,6 +27,7 @@ export namespace TsType { endPropertyWithSemicolon: true, endTypeWithSemicolon: true, propertyDescription: true, + useConstEnums: false, useFullReferencePathAsName: false, useInterfaceDeclaration: true, useTypescriptEnums: false @@ -142,7 +144,7 @@ export class Enum extends TsType { return `${this.toType(settings)}` } toDeclaration(settings: TsTypeSettings): string { - return `${this.toBlockComment(settings)}export enum ${this._type(settings)}{ + return `${this.toBlockComment(settings)}export ${settings.useConstEnums ? "const " : ""}enum ${this._type(settings)}{ ${this.enumValues.map(_ => _.toDeclaration()).join(',\n')} }` } diff --git a/test/cases/enum.ts b/test/cases/enum.ts index 6c66b803..d8f93d77 100644 --- a/test/cases/enum.ts +++ b/test/cases/enum.ts @@ -24,7 +24,7 @@ export var configurations = [ foo: "a" | "b" | "c"; bar: number; }` - }, + }, { settings: { useTypescriptEnums: true @@ -39,6 +39,26 @@ export enum Bar { Two = 2, Three = 3 } +export interface Enum { + foo: Foo; + bar: Bar; +}` + }, + { + settings: { + useTypescriptEnums: true, + useConstEnums: true + }, + types: `export const enum Foo { + a, + b, + c +} +export const enum Bar { + One = 1, + Two = 2, + Three = 3 +} export interface Enum { foo: Foo; bar: Bar; From d08da0ad0414d797aab395f972ffcbe3a286ac61 Mon Sep 17 00:00:00 2001 From: Jonathan Ogilvie Date: Tue, 6 Sep 2016 17:02:39 -0400 Subject: [PATCH 18/27] Fix lint --- src/TsTypes.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/TsTypes.ts b/src/TsTypes.ts index b97c5542..40ef706e 100644 --- a/src/TsTypes.ts +++ b/src/TsTypes.ts @@ -144,7 +144,7 @@ export class Enum extends TsType { return `${this.toType(settings)}` } toDeclaration(settings: TsTypeSettings): string { - return `${this.toBlockComment(settings)}export ${settings.useConstEnums ? "const " : ""}enum ${this._type(settings)}{ + return `${this.toBlockComment(settings)}export ${settings.useConstEnums ? 'const ' : ''}enum ${this._type(settings)}{ ${this.enumValues.map(_ => _.toDeclaration()).join(',\n')} }` } From f024314cc02d81451bc10a9bf64198442ab2a2c0 Mon Sep 17 00:00:00 2001 From: Jonathan Ogilvie Date: Wed, 7 Sep 2016 11:43:06 -0400 Subject: [PATCH 19/27] Moving enum utils; removing setting for using ts enums; using ts enums for int-backed enums and string unions for string-backed --- dist/index.d.ts | 45 +++----- dist/index.js | 268 ++++++++++++++++++++++++--------------------- src/EnumUtils.ts | 57 ++++++++++ src/TsTypes.ts | 154 ++++++++------------------ src/index.ts | 112 ++++++++++--------- test/cases/enum.ts | 70 ++---------- 6 files changed, 335 insertions(+), 371 deletions(-) create mode 100644 src/EnumUtils.ts diff --git a/dist/index.d.ts b/dist/index.d.ts index d6c02ec3..2931fcb1 100644 --- a/dist/index.d.ts +++ b/dist/index.d.ts @@ -68,10 +68,9 @@ export namespace TsType { useConstEnums?: boolean; useFullReferencePathAsName?: boolean; useInterfaceDeclaration?: boolean; - useTypescriptEnums?: boolean; } var DEFAULT_SETTINGS: TsTypeSettings; - abstract class TsType { + abstract class TsTypeBase { id?: string; description?: string; protected safeId(): string | undefined; @@ -87,27 +86,27 @@ export namespace TsType { interface TsProp { name: string; required: boolean; - type: TsType; + type: TsTypeBase; } - class Any extends TsType { + class Any extends TsTypeBase { _type(): string; } - class String extends TsType { + class String extends TsTypeBase { _type(): string; } - class Boolean extends TsType { + class Boolean extends TsTypeBase { _type(): string; } - class Number extends TsType { + class Number extends TsTypeBase { _type(): string; } - class Object extends TsType { + class Object extends TsTypeBase { _type(): string; } - class Void extends TsType { + class Void extends TsTypeBase { _type(): string; } - class Literal extends TsType { + class Literal extends TsTypeBase { constructor(value: any); _type(): any; } @@ -118,7 +117,7 @@ export namespace TsType { toDeclaration(): string; toString(): string; } - class Enum extends TsType { + class Enum extends TsTypeBase { enumValues: EnumValue[]; constructor(enumValues: EnumValue[]); isSimpleType(): boolean; @@ -126,25 +125,13 @@ export namespace TsType { toSafeType(settings: TsTypeSettings): string; toDeclaration(settings: TsTypeSettings): string; } - class EnumUtils extends TsType { - protected enm: Enum; - constructor(enm: Enum); - isSimpleType(): boolean; - _type(settings: TsTypeSettings): string; - toSafeType(settings: TsTypeSettings): string; - toDeclaration(settings: TsTypeSettings): string; - makeValuesMethod(settings: TsTypeSettings): string; - makeFromStringValueMethod(settings: TsTypeSettings): string; - makeToStringValueMethod(settings: TsTypeSettings): string; - makeFromStringValuesMethod(settings: TsTypeSettings): string; - } - class Array extends TsType { - constructor(type?: TsType); + class Array extends TsTypeBase { + constructor(type?: TsTypeBase); _type(settings: TsTypeSettings): string; } - class Intersection extends TsType { - protected data: TsType[]; - constructor(data: TsType[]); + class Intersection extends TsTypeBase { + protected data: TsTypeBase[]; + constructor(data: TsTypeBase[]); isSimpleType(): boolean; _type(settings: TsTypeSettings): string; toSafeType(settings: TsTypeSettings): string; @@ -153,7 +140,7 @@ export namespace TsType { isSimpleType(): boolean; _type(settings: TsTypeSettings): string; } - class Interface extends TsType { + class Interface extends TsTypeBase { constructor(props: TsProp[]); static reference(id: string): Interface; protected _type(settings: TsTypeSettings, declaration?: boolean): string; diff --git a/dist/index.js b/dist/index.js index 0418f2f8..18a524bc 100644 --- a/dist/index.js +++ b/dist/index.js @@ -5,10 +5,57 @@ var __extends = (this && this.__extends) || function (d, b) { function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; +var TsTypes = require('./TsTypes'); +var EnumUtils = (function (_super) { + __extends(EnumUtils, _super); + function EnumUtils(enm) { + _super.call(this); + this.enm = enm; + } + EnumUtils.prototype.isSimpleType = function () { return false; }; + EnumUtils.prototype._type = function (settings) { + // It's a bit hacky, but if this is a top level type, then addDeclaration changes + // our enum type's ID out from under us when it adds the enum to the declaration map, *after* + // the util class is declared. So we name ourselves by our enum's type, not our own ID' + return this.enm.toSafeType(settings) + "Util" || this.safeId() || 'SomeEnumTypeUtils'; + }; + EnumUtils.prototype.toSafeType = function (settings) { + return "" + this.toType(settings); + }; + EnumUtils.prototype.toDeclaration = function (settings) { + return this.toBlockComment(settings) + "export class " + this._type(settings) + " {\n " + this.makeValuesMethod(settings) + "\n " + this.makeToStringValueMethod(settings) + "\n " + this.makeFromStringValueMethod(settings) + "\n " + this.makeFromStringValuesMethod(settings) + "\n }"; + }; + EnumUtils.prototype.makeValuesMethod = function (settings) { + var enumType = this.enm.toSafeType(settings); + return "static values(): " + enumType + "[] {\n return [" + this.enm.enumValues.map(function (_) { return (enumType + "." + _.identifier); }).join(',') + "]\n }"; + }; + EnumUtils.prototype.makeFromStringValueMethod = function (settings) { + var enumType = this.enm.toSafeType(settings); + return "static fromStringValue(value: string): " + enumType + " {\n switch(value.toLowerCase()){\n " + this.enm.enumValues.map(function (_) { return ("case \"" + _.identifier.toLowerCase() + "\":\n return " + (enumType + '.' + _.identifier) + ";"); }).join('\n') + "\n default:\n throw new Error(\"Unrecognized " + enumType + ": \" + value);\n }\n }"; + }; + EnumUtils.prototype.makeToStringValueMethod = function (settings) { + var enumType = this.enm.toSafeType(settings); + return "static toStringValue(enm: " + enumType + "): string {\n switch(enm){\n " + this.enm.enumValues.map(function (_) { return ("case " + (enumType + '.' + _.identifier) + ":\n return \"" + _.identifier.toLowerCase() + "\";"); }).join('\n') + "\n }\n }"; + }; + EnumUtils.prototype.makeFromStringValuesMethod = function (settings) { + var enumType = this.enm.toSafeType(settings); + return "static fromStringValues(values: string[]): " + enumType + "[] {\n return _.map(values, value => " + this._type(settings) + ".fromStringValue(value));\n }"; + }; + return EnumUtils; +}(TsTypes.TsType.TsTypeBase)); +exports.EnumUtils = EnumUtils; + +},{"./TsTypes":2}],2:[function(require,module,exports){ +"use strict"; +var __extends = (this && this.__extends) || function (d, b) { + for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); +}; var lodash_1 = require('lodash'); var TsType; -(function (TsType_1) { - TsType_1.DEFAULT_SETTINGS = { +(function (TsType) { + TsType.DEFAULT_SETTINGS = { addEnumUtils: false, declarationDescription: true, // declareProperties: false, @@ -20,36 +67,35 @@ var TsType; useConstEnums: false, useFullReferencePathAsName: false, useInterfaceDeclaration: true, - useTypescriptEnums: false }; - var TsType = (function () { - function TsType() { + var TsTypeBase = (function () { + function TsTypeBase() { } - TsType.prototype.safeId = function () { + TsTypeBase.prototype.safeId = function () { return this.id && lodash_1.upperFirst(lodash_1.camelCase(this.id)); }; - TsType.prototype.toBlockComment = function (settings) { + TsTypeBase.prototype.toBlockComment = function (settings) { return this.description && settings.declarationDescription ? "/** " + this.description + " */\n" : ''; }; - TsType.prototype._toDeclaration = function (decl, settings) { + TsTypeBase.prototype._toDeclaration = function (decl, settings) { return this.toBlockComment(settings) + decl + (settings.endTypeWithSemicolon ? ';' : ''); }; - TsType.prototype.isSimpleType = function () { return true; }; - TsType.prototype.toDeclaration = function (settings) { - return this._toDeclaration("type " + this.safeId() + " = " + this._type(settings), settings); + TsTypeBase.prototype.isSimpleType = function () { return true; }; + TsTypeBase.prototype.toDeclaration = function (settings) { + return this._toDeclaration("export type " + this.safeId() + " = " + this._type(settings), settings); }; - TsType.prototype.toSafeType = function (settings) { + TsTypeBase.prototype.toSafeType = function (settings) { return this.toType(settings); }; - TsType.prototype.toType = function (settings) { + TsTypeBase.prototype.toType = function (settings) { return this.safeId() || this._type(settings); }; - TsType.prototype.toString = function () { - return this._type(TsType_1.DEFAULT_SETTINGS); + TsTypeBase.prototype.toString = function () { + return this._type(TsType.DEFAULT_SETTINGS); }; - return TsType; + return TsTypeBase; }()); - TsType_1.TsType = TsType; + TsType.TsTypeBase = TsTypeBase; var Any = (function (_super) { __extends(Any, _super); function Any() { @@ -59,8 +105,8 @@ var TsType; return 'any'; }; return Any; - }(TsType)); - TsType_1.Any = Any; + }(TsTypeBase)); + TsType.Any = Any; var String = (function (_super) { __extends(String, _super); function String() { @@ -70,8 +116,8 @@ var TsType; return 'string'; }; return String; - }(TsType)); - TsType_1.String = String; + }(TsTypeBase)); + TsType.String = String; var Boolean = (function (_super) { __extends(Boolean, _super); function Boolean() { @@ -81,8 +127,8 @@ var TsType; return 'boolean'; }; return Boolean; - }(TsType)); - TsType_1.Boolean = Boolean; + }(TsTypeBase)); + TsType.Boolean = Boolean; var Number = (function (_super) { __extends(Number, _super); function Number() { @@ -92,8 +138,8 @@ var TsType; return 'number'; }; return Number; - }(TsType)); - TsType_1.Number = Number; + }(TsTypeBase)); + TsType.Number = Number; var Object = (function (_super) { __extends(Object, _super); function Object() { @@ -103,8 +149,8 @@ var TsType; return 'Object'; }; return Object; - }(TsType)); - TsType_1.Object = Object; + }(TsTypeBase)); + TsType.Object = Object; var Void = (function (_super) { __extends(Void, _super); function Void() { @@ -114,8 +160,8 @@ var TsType; return 'void'; }; return Void; - }(TsType)); - TsType_1.Void = Void; + }(TsTypeBase)); + TsType.Void = Void; var Literal = (function (_super) { __extends(Literal, _super); function Literal(value) { @@ -126,8 +172,8 @@ var TsType; return this.value; }; return Literal; - }(TsType)); - TsType_1.Literal = Literal; + }(TsTypeBase)); + TsType.Literal = Literal; var EnumValue = (function () { function EnumValue(enumValues) { var hasValue = !!enumValues[0]; @@ -150,7 +196,7 @@ var TsType; }; return EnumValue; }()); - TsType_1.EnumValue = EnumValue; + TsType.EnumValue = EnumValue; var Enum = (function (_super) { __extends(Enum, _super); function Enum(enumValues) { @@ -165,49 +211,11 @@ var TsType; return "" + this.toType(settings); }; Enum.prototype.toDeclaration = function (settings) { - return this.toBlockComment(settings) + "export " + (settings.useConstEnums ? "const " : "") + "enum " + this._type(settings) + "{\n " + this.enumValues.map(function (_) { return _.toDeclaration(); }).join(',\n') + "\n }"; + return this.toBlockComment(settings) + "export " + (settings.useConstEnums ? 'const ' : '') + "enum " + this._type(settings) + "{\n " + this.enumValues.map(function (_) { return _.toDeclaration(); }).join(',\n') + "\n }"; }; return Enum; - }(TsType)); - TsType_1.Enum = Enum; - var EnumUtils = (function (_super) { - __extends(EnumUtils, _super); - function EnumUtils(enm) { - _super.call(this); - this.enm = enm; - } - EnumUtils.prototype.isSimpleType = function () { return false; }; - EnumUtils.prototype._type = function (settings) { - // It's a bit hacky, but if this is a top level type, then addDeclaration changes - // our enum type's ID out from under us when it adds the enum to the declaration map, *after* - // the util class is declared. So we name ourselves by our enum's type, not our own ID' - return this.enm.toSafeType(settings) + "Util" || this.safeId() || 'SomeEnumTypeUtils'; - }; - EnumUtils.prototype.toSafeType = function (settings) { - return "" + this.toType(settings); - }; - EnumUtils.prototype.toDeclaration = function (settings) { - return this.toBlockComment(settings) + "export class " + this._type(settings) + " {\n " + this.makeValuesMethod(settings) + "\n " + this.makeToStringValueMethod(settings) + "\n " + this.makeFromStringValueMethod(settings) + "\n " + this.makeFromStringValuesMethod(settings) + "\n }"; - }; - EnumUtils.prototype.makeValuesMethod = function (settings) { - var enumType = this.enm.toSafeType(settings); - return "static values(): " + enumType + "[] {\n return [" + this.enm.enumValues.map(function (_) { return (enumType + "." + _.identifier); }).join(',') + "]\n }"; - }; - EnumUtils.prototype.makeFromStringValueMethod = function (settings) { - var enumType = this.enm.toSafeType(settings); - return "static fromStringValue(value: string): " + enumType + " {\n switch(value.toLowerCase()){\n " + this.enm.enumValues.map(function (_) { return ("case \"" + _.identifier.toLowerCase() + "\":\n return " + (enumType + '.' + _.identifier) + ";"); }).join('\n') + "\n default:\n throw new Error(\"Unrecognized " + enumType + ": \" + value);\n }\n }"; - }; - EnumUtils.prototype.makeToStringValueMethod = function (settings) { - var enumType = this.enm.toSafeType(settings); - return "static toStringValue(enm: " + enumType + "): string {\n switch(enm){\n " + this.enm.enumValues.map(function (_) { return ("case " + (enumType + '.' + _.identifier) + ":\n return \"" + _.identifier.toLowerCase() + "\";"); }).join('\n') + "\n }\n }"; - }; - EnumUtils.prototype.makeFromStringValuesMethod = function (settings) { - var enumType = this.enm.toSafeType(settings); - return "static fromStringValues(values: string[]): " + enumType + "[] {\n return _.map(values, value => " + this._type(settings) + ".fromStringValue(value));\n }"; - }; - return EnumUtils; - }(TsType)); - TsType_1.EnumUtils = EnumUtils; + }(TsTypeBase)); + TsType.Enum = Enum; var Array = (function (_super) { __extends(Array, _super); function Array(type) { @@ -218,8 +226,8 @@ var TsType; return (this.type || new Any()).toSafeType(settings) + "[]"; }; return Array; - }(TsType)); - TsType_1.Array = Array; + }(TsTypeBase)); + TsType.Array = Array; var Intersection = (function (_super) { __extends(Intersection, _super); function Intersection(data) { @@ -237,8 +245,8 @@ var TsType; return "" + this.toType(settings); }; return Intersection; - }(TsType)); - TsType_1.Intersection = Intersection; + }(TsTypeBase)); + TsType.Intersection = Intersection; var Union = (function (_super) { __extends(Union, _super); function Union() { @@ -252,7 +260,7 @@ var TsType; }; return Union; }(Intersection)); - TsType_1.Union = Union; + TsType.Union = Union; var Interface = (function (_super) { __extends(Interface, _super); function Interface(props) { @@ -274,7 +282,7 @@ var TsType; decl += ': ' + _.type.toType(settings); if (settings.endPropertyWithSemicolon) decl += ';'; - if (settings.propertyDescription && _.type.description) + if (settings.propertyDescription && _.type.description && !_.type.id) decl += ' // ' + _.type.description; return decl; }).join('\n') + "\n}" : id; @@ -283,15 +291,16 @@ var TsType; Interface.prototype.toDeclaration = function (settings) { if (settings.useInterfaceDeclaration) return this.toBlockComment(settings) + "export interface " + this.safeId() + " " + this._type(settings, true); - return this._toDeclaration("type " + this.safeId() + " = " + this._type(settings, true), settings); + return this._toDeclaration("export type " + this.safeId() + " = " + this._type(settings, true), settings); }; return Interface; - }(TsType)); - TsType_1.Interface = Interface; + }(TsTypeBase)); + TsType.Interface = Interface; })(TsType = exports.TsType || (exports.TsType = {})); -},{"lodash":undefined}],2:[function(require,module,exports){ +},{"lodash":undefined}],3:[function(require,module,exports){ "use strict"; +var EnumUtils_1 = require('./EnumUtils'); var pretty_printer_1 = require('./pretty-printer'); var TsTypes_1 = require('./TsTypes'); var fs_1 = require('fs'); @@ -315,6 +324,11 @@ var RuleType; RuleType[RuleType['Boolean'] = 13] = 'Boolean'; RuleType[RuleType['Literal'] = 14] = 'Literal'; })(RuleType || (RuleType = {})); +var EnumType; +(function (EnumType) { + EnumType[EnumType["String"] = 0] = "String"; + EnumType[EnumType["Integer"] = 1] = "Integer"; +})(EnumType || (EnumType = {})); var Compiler = (function () { function Compiler(schema, filePath, settings) { this.schema = schema; @@ -446,40 +460,39 @@ var Compiler = (function () { case RuleType.NamedSchema: return this.toTsDeclaration(rule); case RuleType.Enum: - // TODO: honor the schema's "type" on the enum. if string, - // skip all the zipping mess; if int, either require the tsEnumNames - // or generate literals for the values - if (this.settings.useTypescriptEnums) { - this.validateEnumMembers(rule); - var enumValues = lodash_1.zip(rule.tsEnumNames || [], - // If we try to create a literal from an object, bad stuff can happen... so we have to toString it - rule.enum.map(function (_) { return new TsTypes_1.TsType.Literal(_).toType(_this.settings).toString(); })) - .map(function (_) { return new TsTypes_1.TsType.EnumValue(_); }); - // name our anonymous enum, if it doesn't have an ID, by the property name under - // which it was declared. Failing both of these things, it'll concat together the - // identifiers as EnumOneTwoThree for enum: ["One", "Two", "Three"]. Ugly, but - // practical. - var path = rule.id || propName || ('Enum' + enumValues.map(function (_) { return _.identifier; }).join('')); - var enm = new TsTypes_1.TsType.Enum(enumValues); - var retVal = enm; - // don't add this to the declarations map if this is the top-level type (already declared) - // or if it's a reference and we don't want to declare those. - if ((!isReference || this.settings.declareReferenced)) { - if (!isTop) { - retVal = this.declareType(retVal, path, path); - } - else { - retVal.id = path; + // we honor the schema's "type" on the enum. if string, generate a union. + // if int, require the tsEnumNames + var enumType = this.validateEnumMembers(rule); + switch (enumType) { + case EnumType.Integer: + var enumValues = lodash_1.zip(rule.tsEnumNames || [], + // If we try to create a literal from an object, bad stuff can happen... so we have to toString it + rule.enum.map(function (_) { return new TsTypes_1.TsType.Literal(_).toType(_this.settings).toString(); })) + .map(function (_) { return new TsTypes_1.TsType.EnumValue(_); }); + // name our anonymous enum, if it doesn't have an ID, by the property name under + // which it was declared. Failing both of these things, it'll concat together the + // identifiers as EnumOneTwoThree for enum: ["One", "Two", "Three"]. Ugly, but + // practical. + var path = rule.id || propName || ('Enum' + enumValues.map(function (_) { return _.identifier; }).join('')); + var enm = new TsTypes_1.TsType.Enum(enumValues); + var retVal = enm; + // don't add this to the declarations map if this is the top-level type (already declared) + // or if it's a reference and we don't want to declare those. + if ((!isReference || this.settings.declareReferenced)) { + if (!isTop) { + retVal = this.declareType(retVal, path, path); + } + else { + retVal.id = path; + } + if (this.settings.addEnumUtils) { + var utilPath = path + 'Utils'; + this.declareType(new EnumUtils_1.EnumUtils(enm), utilPath, utilPath); + } } - if (this.settings.addEnumUtils) { - var utilPath = path + 'Utils'; - this.declareType(new TsTypes_1.TsType.EnumUtils(enm), utilPath, utilPath); - } - } - return retVal; - } - else { - return new TsTypes_1.TsType.Union(lodash_1.uniqBy(rule.enum.map(function (_) { return _this.toStringLiteral(_); }), function (_) { return _.toType(_this.settings); })); + return retVal; + case EnumType.String: + return new TsTypes_1.TsType.Union(lodash_1.uniqBy(rule.enum.map(function (_) { return _this.toStringLiteral(_); }), function (_) { return _.toType(_this.settings); })); } case RuleType.Any: return new TsTypes_1.TsType.Any; case RuleType.Literal: return new TsTypes_1.TsType.Literal(rule); @@ -505,10 +518,10 @@ var Compiler = (function () { var isDeclaredStringEnum = rule.type === 'string'; var isDeclaredIntegerEnum = rule.type === 'integer'; if (!isDeclaredStringEnum && !isDeclaredIntegerEnum) { - throw TypeError('Enum type must be string or integer when useTypescriptEnums=true; default is string if undefined'); + throw TypeError('Enum type must be string or integer; default is string if undefined'); } if (rule.enum.some(function (_) { return _ instanceof Object; })) { - throw TypeError('Enum members must be a list of strings or a list of integers when useTypescriptEnums=true; instead, found an Object'); + throw TypeError('Enum members must be a list of strings or a list of integers; instead, found an Object'); } var isActuallyStringEnum = rule.enum.every(function (_) { return typeof (_) === 'string'; }); var isActuallyIntegerEnum = rule.enum.every(function (_) { return typeof (_) === 'number'; }); @@ -533,14 +546,25 @@ var Compiler = (function () { } // I don't think we should ever hit this case. if (!isActuallyStringEnum && !isIntegerEnumWithValidStringValues) { - throw TypeError('Enum members must be a list of strings or a list of integers (with corresponding tsEnumValues) when useTypescriptEnums=true'); + throw TypeError('Enum members must be a list of strings or a list of integers (with corresponding tsEnumValues)'); + } + if (isIntegerEnumWithValidStringValues) { + return EnumType.Integer; + } + else { + return EnumType.String; } }; Compiler.prototype.toTsType = function (rule, propName, isTop, isReference) { if (isTop === void 0) { isTop = false; } if (isReference === void 0) { isReference = false; } var type = this.createTsType(rule, propName, isTop, isReference); - type.id = type.id || rule.id || rule.title; + if (!type.id) { + // the type is not declared, let's check if we should declare it or keep it inline + type.id = rule.id || rule.title; + if (type.id && !isReference) + this.declareType(type, type.id, type.id); + } type.description = type.description || rule.description; return type; }; @@ -598,7 +622,7 @@ function compileFromFile(inputFilename) { } exports.compileFromFile = compileFromFile; -},{"./TsTypes":1,"./pretty-printer":3,"fs":undefined,"lodash":undefined,"path":undefined}],3:[function(require,module,exports){ +},{"./EnumUtils":1,"./TsTypes":2,"./pretty-printer":4,"fs":undefined,"lodash":undefined,"path":undefined}],4:[function(require,module,exports){ // from https://github.com/Microsoft/TypeScript/wiki/Using-the-Compiler-API#pretty-printer-using-the-ls-formatter "use strict"; var ts = require('typescript'); @@ -650,5 +674,5 @@ function format(text) { } exports.format = format; -},{"typescript":undefined}]},{},[2])(2) +},{"typescript":undefined}]},{},[3])(3) }); \ No newline at end of file diff --git a/src/EnumUtils.ts b/src/EnumUtils.ts new file mode 100644 index 00000000..5fca33e4 --- /dev/null +++ b/src/EnumUtils.ts @@ -0,0 +1,57 @@ +import * as TsTypes from './TsTypes' + +export class EnumUtils extends TsTypes.TsType.TsTypeBase { + constructor(protected enm: TsTypes.TsType.Enum) { + super() + } + isSimpleType() { return false } + _type(settings: TsTypes.TsType.TsTypeSettings) { + // It's a bit hacky, but if this is a top level type, then addDeclaration changes + // our enum type's ID out from under us when it adds the enum to the declaration map, *after* + // the util class is declared. So we name ourselves by our enum's type, not our own ID' + return `${this.enm.toSafeType(settings)}Util` || this.safeId() || 'SomeEnumTypeUtils' + } + toSafeType(settings: TsTypes.TsType.TsTypeSettings) { + return `${this.toType(settings)}` + } + toDeclaration(settings: TsTypes.TsType.TsTypeSettings): string { + return `${this.toBlockComment(settings)}export class ${this._type(settings)} { + ${this.makeValuesMethod(settings)} + ${this.makeToStringValueMethod(settings)} + ${this.makeFromStringValueMethod(settings)} + ${this.makeFromStringValuesMethod(settings)} + }` + } + makeValuesMethod(settings: TsTypes.TsType.TsTypeSettings){ + let enumType = this.enm.toSafeType(settings) + return `static values(): ${enumType}[] { + return [${this.enm.enumValues.map(_ => `${enumType}.${_.identifier}`).join(',')}] + }` + } + makeFromStringValueMethod(settings: TsTypes.TsType.TsTypeSettings){ + let enumType = this.enm.toSafeType(settings) + return `static fromStringValue(value: string): ${enumType} { + switch(value.toLowerCase()){ + ${this.enm.enumValues.map(_ => `case "${_.identifier.toLowerCase()}": + return ${enumType + '.' + _.identifier};`).join('\n')} + default: + throw new Error("Unrecognized ${enumType}: " + value); + } + }` + } + makeToStringValueMethod(settings: TsTypes.TsType.TsTypeSettings){ + let enumType = this.enm.toSafeType(settings) + return `static toStringValue(enm: ${enumType}): string { + switch(enm){ + ${this.enm.enumValues.map(_ => `case ${enumType + '.' + _.identifier}: + return "${_.identifier.toLowerCase()}";`).join('\n')} + } + }` + } + makeFromStringValuesMethod(settings: TsTypes.TsType.TsTypeSettings){ + let enumType = this.enm.toSafeType(settings) + return `static fromStringValues(values: string[]): ${enumType}[] { + return _.map(values, value => ${this._type(settings)}.fromStringValue(value)); + }` + } +} diff --git a/src/TsTypes.ts b/src/TsTypes.ts index 36b9bbd8..32cd4329 100644 --- a/src/TsTypes.ts +++ b/src/TsTypes.ts @@ -15,7 +15,6 @@ export namespace TsType { useConstEnums?: boolean useFullReferencePathAsName?: boolean useInterfaceDeclaration?: boolean - useTypescriptEnums?: boolean } export var DEFAULT_SETTINGS: TsTypeSettings = { @@ -30,10 +29,9 @@ export namespace TsType { useConstEnums: false, useFullReferencePathAsName: false, useInterfaceDeclaration: true, - useTypescriptEnums: false } - export abstract class TsType { + export abstract class TsTypeBase { id?: string description?: string @@ -65,155 +63,99 @@ export namespace TsType { export interface TsProp { name: string required: boolean - type: TsType + type: TsTypeBase } - export class Any extends TsType { + export class Any extends TsTypeBase { _type() { return 'any' } } - export class String extends TsType { + export class String extends TsTypeBase { _type() { return 'string' } } - export class Boolean extends TsType { + export class Boolean extends TsTypeBase { _type() { return 'boolean' } } - export class Number extends TsType { + export class Number extends TsTypeBase { _type() { return 'number' } } - export class Object extends TsType { + export class Object extends TsTypeBase { _type() { return 'Object' } } - export class Void extends TsType { + export class Void extends TsTypeBase { _type() { return 'void' } } - export class Literal extends TsType { + export class Literal extends TsTypeBase { constructor(private value: any) { super() } _type() { return this.value } } -export class EnumValue { - identifier: string - value: string + export class EnumValue { + identifier: string + value: string - constructor(enumValues: string[]) { - let hasValue = !!enumValues[0] + constructor(enumValues: string[]) { + let hasValue = !!enumValues[0] - // quirky propagation logic - if (hasValue){ - this.identifier = enumValues[0] - this.value = enumValues[1] - } else { - this.identifier = enumValues[1] + // quirky propagation logic + if (hasValue){ + this.identifier = enumValues[0] + this.value = enumValues[1] + } else { + this.identifier = enumValues[1] + } } - } - - toDeclaration(){ - // if there is a value associated with the identifier, declare as identifier=value - // else declare as identifier - return `${this.identifier}${this.value ? ('=' + this.value) : ''}` - } - toString(){ - return `Enum${this.identifier}` - } -} + toDeclaration(){ + // if there is a value associated with the identifier, declare as identifier=value + // else declare as identifier + return `${this.identifier}${this.value ? ('=' + this.value) : ''}` + } -export class Enum extends TsType { - constructor(public enumValues: EnumValue[]) { - super() - } - isSimpleType() { return false } - _type(settings: TsTypeSettings) { - return this.safeId() || 'SomeEnumType' - } - toSafeType(settings: TsTypeSettings) { - return `${this.toType(settings)}` - } - toDeclaration(settings: TsTypeSettings): string { - return `${this.toBlockComment(settings)}export ${settings.useConstEnums ? 'const ' : ''}enum ${this._type(settings)}{ - ${this.enumValues.map(_ => _.toDeclaration()).join(',\n')} - }` + toString(){ + return `Enum${this.identifier}` + } } -} -export class EnumUtils extends TsType { - constructor(protected enm: Enum) { - super() - } - isSimpleType() { return false } - _type(settings: TsTypeSettings) { - // It's a bit hacky, but if this is a top level type, then addDeclaration changes - // our enum type's ID out from under us when it adds the enum to the declaration map, *after* - // the util class is declared. So we name ourselves by our enum's type, not our own ID' - return `${this.enm.toSafeType(settings)}Util` || this.safeId() || 'SomeEnumTypeUtils' - } - toSafeType(settings: TsTypeSettings) { - return `${this.toType(settings)}` - } - toDeclaration(settings: TsTypeSettings): string { - return `${this.toBlockComment(settings)}export class ${this._type(settings)} { - ${this.makeValuesMethod(settings)} - ${this.makeToStringValueMethod(settings)} - ${this.makeFromStringValueMethod(settings)} - ${this.makeFromStringValuesMethod(settings)} - }` - } - makeValuesMethod(settings: TsTypeSettings){ - let enumType = this.enm.toSafeType(settings) - return `static values(): ${enumType}[] { - return [${this.enm.enumValues.map(_ => `${enumType}.${_.identifier}`).join(',')}] - }` - } - makeFromStringValueMethod(settings: TsTypeSettings){ - let enumType = this.enm.toSafeType(settings) - return `static fromStringValue(value: string): ${enumType} { - switch(value.toLowerCase()){ - ${this.enm.enumValues.map(_ => `case "${_.identifier.toLowerCase()}": - return ${enumType + '.' + _.identifier};`).join('\n')} - default: - throw new Error("Unrecognized ${enumType}: " + value); + export class Enum extends TsTypeBase { + constructor(public enumValues: EnumValue[]) { + super() } - }` - } - makeToStringValueMethod(settings: TsTypeSettings){ - let enumType = this.enm.toSafeType(settings) - return `static toStringValue(enm: ${enumType}): string { - switch(enm){ - ${this.enm.enumValues.map(_ => `case ${enumType + '.' + _.identifier}: - return "${_.identifier.toLowerCase()}";`).join('\n')} + isSimpleType() { return false } + _type(settings: TsTypeSettings) { + return this.safeId() || 'SomeEnumType' + } + toSafeType(settings: TsTypeSettings) { + return `${this.toType(settings)}` + } + toDeclaration(settings: TsTypeSettings): string { + return `${this.toBlockComment(settings)}export ${settings.useConstEnums ? 'const ' : ''}enum ${this._type(settings)}{ + ${this.enumValues.map(_ => _.toDeclaration()).join(',\n')} + }` } - }` - } - makeFromStringValuesMethod(settings: TsTypeSettings){ - let enumType = this.enm.toSafeType(settings) - return `static fromStringValues(values: string[]): ${enumType}[] { - return _.map(values, value => ${this._type(settings)}.fromStringValue(value)); - }` } -} - export class Array extends TsType { - constructor(private type?: TsType) { super() } + export class Array extends TsTypeBase { + constructor(private type?: TsTypeBase) { super() } _type(settings: TsTypeSettings) { return `${(this.type || new Any()).toSafeType(settings)}[]` } } - export class Intersection extends TsType { - constructor(protected data: TsType[]) { + export class Intersection extends TsTypeBase { + constructor(protected data: TsTypeBase[]) { super() } isSimpleType() { return this.data.filter(_ => !(_ instanceof Void)).length <= 1 } @@ -236,7 +178,7 @@ export class EnumUtils extends TsType { } } - export class Interface extends TsType { + export class Interface extends TsTypeBase { constructor(private props: TsProp[]) { super() } diff --git a/src/index.ts b/src/index.ts index 0f0fa389..0814e61b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,3 +1,4 @@ +import { EnumUtils } from './EnumUtils' import { JSONSchema } from './JSONSchema' import { format } from './pretty-printer' import { TsType } from './TsTypes' @@ -10,6 +11,11 @@ enum RuleType { 'String', 'Number', 'Void', 'Object', 'Array', 'Boolean', 'Literal' } +enum EnumType { + String, + Integer +} + class Compiler { static DEFAULT_SETTINGS = TsType.DEFAULT_SETTINGS @@ -39,7 +45,7 @@ class Compiler { private settings: TsType.TsTypeSettings private id: string - private declarations: Map + private declarations: Map private filePath: ParsedPath private isRequired(propertyName: string, schema: JSONSchema.Schema): boolean { @@ -96,7 +102,7 @@ class Compiler { // eg. "#/definitions/diskDevice" => ["definitions", "diskDevice"] // only called in case of a $ref type - private resolveType(refPath: string, propName: string): TsType.TsType { + private resolveType(refPath: string, propName: string): TsType.TsTypeBase { if (refPath === '#' || refPath === '#/'){ return TsType.Interface.reference(this.id) } @@ -135,13 +141,13 @@ class Compiler { return ret } - private declareType(type: TsType.TsType, refPath: string, id: string) { + private declareType(type: TsType.TsTypeBase, refPath: string, id: string) { type.id = id this.declarations.set(refPath, type) return type } - private toStringLiteral(a: boolean | number | string | Object): TsType.TsType { + private toStringLiteral(a: boolean | number | string | Object): TsType.TsTypeBase { switch (typeof 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 @@ -161,54 +167,52 @@ class Compiler { } } - private createTsType (rule: JSONSchema.Schema, propName?: string, isTop: boolean = false, isReference: boolean = false): TsType.TsType { + private createTsType (rule: JSONSchema.Schema, propName?: string, isTop: boolean = false, isReference: boolean = false): TsType.TsTypeBase { switch (this.getRuleType(rule)) { case RuleType.AnonymousSchema: case RuleType.NamedSchema: return this.toTsDeclaration(rule) case RuleType.Enum: - // TODO: honor the schema's "type" on the enum. if string, - // skip all the zipping mess; if int, either require the tsEnumNames - // or generate literals for the values - - if (this.settings.useTypescriptEnums){ - this.validateEnumMembers(rule) - - var enumValues = zip(rule.tsEnumNames || [], - // If we try to create a literal from an object, bad stuff can happen... so we have to toString it - rule.enum!.map(_ => new TsType.Literal(_).toType(this.settings).toString())) - .map(_ => new TsType.EnumValue(_)) - - // name our anonymous enum, if it doesn't have an ID, by the property name under - // which it was declared. Failing both of these things, it'll concat together the - // identifiers as EnumOneTwoThree for enum: ["One", "Two", "Three"]. Ugly, but - // practical. - let path = rule.id || propName || ('Enum' + enumValues.map(_ => _.identifier).join('')) - - let enm = new TsType.Enum(enumValues) - let retVal: TsType.TsType = enm - - // don't add this to the declarations map if this is the top-level type (already declared) - // or if it's a reference and we don't want to declare those. - if ((!isReference || this.settings.declareReferenced)){ - if (!isTop){ - retVal = this.declareType(retVal, path, path) - } else { - retVal.id = path - } - - if (this.settings.addEnumUtils){ - let utilPath = path + 'Utils' - this.declareType(new TsType.EnumUtils(enm), utilPath, utilPath) + // we honor the schema's "type" on the enum. if string, generate a union. + // if int, require the tsEnumNames + let enumType = this.validateEnumMembers(rule) + + switch (enumType){ + case EnumType.Integer: + var enumValues = zip(rule.tsEnumNames || [], + // If we try to create a literal from an object, bad stuff can happen... so we have to toString it + rule.enum!.map(_ => new TsType.Literal(_).toType(this.settings).toString())) + .map(_ => new TsType.EnumValue(_)) + + // name our anonymous enum, if it doesn't have an ID, by the property name under + // which it was declared. Failing both of these things, it'll concat together the + // identifiers as EnumOneTwoThree for enum: ["One", "Two", "Three"]. Ugly, but + // practical. + let path = rule.id || propName || ('Enum' + enumValues.map(_ => _.identifier).join('')) + + let enm = new TsType.Enum(enumValues) + let retVal: TsType.TsTypeBase = enm + + // don't add this to the declarations map if this is the top-level type (already declared) + // or if it's a reference and we don't want to declare those. + if ((!isReference || this.settings.declareReferenced)){ + if (!isTop){ + retVal = this.declareType(retVal, path, path) + } else { + retVal.id = path + } + + if (this.settings.addEnumUtils){ + let utilPath = path + 'Utils' + this.declareType(new EnumUtils(enm), utilPath, utilPath) + } } - } - - return retVal - } else { - return new TsType.Union(uniqBy( - rule.enum!.map(_ => this.toStringLiteral(_)) - , _ => _.toType(this.settings))) + return retVal + case EnumType.String: + return new TsType.Union(uniqBy( + rule.enum!.map(_ => this.toStringLiteral(_)) + , _ => _.toType(this.settings))) } case RuleType.Any: return new TsType.Any case RuleType.Literal: return new TsType.Literal(rule) @@ -229,18 +233,18 @@ class Compiler { throw new Error('Unknown rule:' + rule.toString()) } - private validateEnumMembers(rule: JSONSchema.Schema){ + private validateEnumMembers(rule: JSONSchema.Schema): EnumType { if (!rule.type) rule.type = 'string' let isDeclaredStringEnum = rule.type === 'string' let isDeclaredIntegerEnum = rule.type === 'integer' if (!isDeclaredStringEnum && !isDeclaredIntegerEnum){ - throw TypeError('Enum type must be string or integer when useTypescriptEnums=true; default is string if undefined') + throw TypeError('Enum type must be string or integer; default is string if undefined') } if (rule.enum!.some(_ => _ instanceof Object)){ - throw TypeError('Enum members must be a list of strings or a list of integers when useTypescriptEnums=true; instead, found an Object') + throw TypeError('Enum members must be a list of strings or a list of integers; instead, found an Object') } let isActuallyStringEnum = rule.enum!.every(_ => typeof(_) === 'string') @@ -270,11 +274,17 @@ class Compiler { // I don't think we should ever hit this case. if (!isActuallyStringEnum && !isIntegerEnumWithValidStringValues){ - throw TypeError('Enum members must be a list of strings or a list of integers (with corresponding tsEnumValues) when useTypescriptEnums=true') + throw TypeError('Enum members must be a list of strings or a list of integers (with corresponding tsEnumValues)') + } + + if (isIntegerEnumWithValidStringValues){ + return EnumType.Integer + } else { + return EnumType.String } } - private toTsType (rule: JSONSchema.Schema, propName?: string, isTop: boolean = false, isReference: boolean = false): TsType.TsType { + private toTsType (rule: JSONSchema.Schema, propName?: string, isTop: boolean = false, isReference: boolean = false): TsType.TsTypeBase { let type = this.createTsType(rule, propName, isTop, isReference) if (!type.id) { // the type is not declared, let's check if we should declare it or keep it inline @@ -285,7 +295,7 @@ class Compiler { type.description = type.description || rule.description return type } - private toTsDeclaration(schema: JSONSchema.Schema): TsType.TsType { + private toTsDeclaration(schema: JSONSchema.Schema): TsType.TsTypeBase { let copy = merge({}, Compiler.DEFAULT_SCHEMA, schema) let props = map( copy.properties!, diff --git a/test/cases/enum.ts b/test/cases/enum.ts index d8f93d77..f445f11b 100644 --- a/test/cases/enum.ts +++ b/test/cases/enum.ts @@ -18,93 +18,37 @@ export var schema = { export var configurations = [ { settings: { - useTypescriptEnums: false + useConstEnums: false }, - types: `export interface Enum { - foo: "a" | "b" | "c"; - bar: number; -}` - }, - { - settings: { - useTypescriptEnums: true - }, - types: `export enum Foo { - a, - b, - c -} -export enum Bar { + types: `export enum Bar { One = 1, Two = 2, Three = 3 } export interface Enum { - foo: Foo; + foo: "a" | "b" | "c"; bar: Bar; }` }, { settings: { - useTypescriptEnums: true, useConstEnums: true }, - types: `export const enum Foo { - a, - b, - c -} -export const enum Bar { + types: `export const enum Bar { One = 1, Two = 2, Three = 3 } export interface Enum { - foo: Foo; + foo: "a" | "b" | "c"; bar: Bar; }` }, { settings: { - useTypescriptEnums: true, addEnumUtils: true }, - types: `export enum Foo { - a, - b, - c -} -export class FooUtil { - static values(): Foo[] { - return [Foo.a, Foo.b, Foo.c] - } - static toStringValue(enm: Foo): string { - switch (enm) { - case Foo.a: - return "a"; - case Foo.b: - return "b"; - case Foo.c: - return "c"; - } - } - static fromStringValue(value: string): Foo { - switch (value.toLowerCase()) { - case "a": - return Foo.a; - case "b": - return Foo.b; - case "c": - return Foo.c; - default: - throw new Error("Unrecognized Foo: " + value); - } - } - static fromStringValues(values: string[]): Foo[] { - return _.map(values, value => FooUtil.fromStringValue(value)); - } -} -export enum Bar { + types: `export enum Bar { One = 1, Two = 2, Three = 3 @@ -140,7 +84,7 @@ export class BarUtil { } } export interface Enum { - foo: Foo; + foo: "a" | "b" | "c"; bar: Bar; }` } From c40f07f4861c69c9895995a40aa5ef7455c6dd38 Mon Sep 17 00:00:00 2001 From: Jonathan Ogilvie Date: Wed, 7 Sep 2016 11:46:15 -0400 Subject: [PATCH 20/27] Removing enum utils --- src/EnumUtils.ts | 57 ---------------------------------------------- src/TsTypes.ts | 4 +--- src/index.ts | 6 ----- test/cases/enum.ts | 44 ----------------------------------- 4 files changed, 1 insertion(+), 110 deletions(-) delete mode 100644 src/EnumUtils.ts diff --git a/src/EnumUtils.ts b/src/EnumUtils.ts deleted file mode 100644 index 5fca33e4..00000000 --- a/src/EnumUtils.ts +++ /dev/null @@ -1,57 +0,0 @@ -import * as TsTypes from './TsTypes' - -export class EnumUtils extends TsTypes.TsType.TsTypeBase { - constructor(protected enm: TsTypes.TsType.Enum) { - super() - } - isSimpleType() { return false } - _type(settings: TsTypes.TsType.TsTypeSettings) { - // It's a bit hacky, but if this is a top level type, then addDeclaration changes - // our enum type's ID out from under us when it adds the enum to the declaration map, *after* - // the util class is declared. So we name ourselves by our enum's type, not our own ID' - return `${this.enm.toSafeType(settings)}Util` || this.safeId() || 'SomeEnumTypeUtils' - } - toSafeType(settings: TsTypes.TsType.TsTypeSettings) { - return `${this.toType(settings)}` - } - toDeclaration(settings: TsTypes.TsType.TsTypeSettings): string { - return `${this.toBlockComment(settings)}export class ${this._type(settings)} { - ${this.makeValuesMethod(settings)} - ${this.makeToStringValueMethod(settings)} - ${this.makeFromStringValueMethod(settings)} - ${this.makeFromStringValuesMethod(settings)} - }` - } - makeValuesMethod(settings: TsTypes.TsType.TsTypeSettings){ - let enumType = this.enm.toSafeType(settings) - return `static values(): ${enumType}[] { - return [${this.enm.enumValues.map(_ => `${enumType}.${_.identifier}`).join(',')}] - }` - } - makeFromStringValueMethod(settings: TsTypes.TsType.TsTypeSettings){ - let enumType = this.enm.toSafeType(settings) - return `static fromStringValue(value: string): ${enumType} { - switch(value.toLowerCase()){ - ${this.enm.enumValues.map(_ => `case "${_.identifier.toLowerCase()}": - return ${enumType + '.' + _.identifier};`).join('\n')} - default: - throw new Error("Unrecognized ${enumType}: " + value); - } - }` - } - makeToStringValueMethod(settings: TsTypes.TsType.TsTypeSettings){ - let enumType = this.enm.toSafeType(settings) - return `static toStringValue(enm: ${enumType}): string { - switch(enm){ - ${this.enm.enumValues.map(_ => `case ${enumType + '.' + _.identifier}: - return "${_.identifier.toLowerCase()}";`).join('\n')} - } - }` - } - makeFromStringValuesMethod(settings: TsTypes.TsType.TsTypeSettings){ - let enumType = this.enm.toSafeType(settings) - return `static fromStringValues(values: string[]): ${enumType}[] { - return _.map(values, value => ${this._type(settings)}.fromStringValue(value)); - }` - } -} diff --git a/src/TsTypes.ts b/src/TsTypes.ts index 32cd4329..101b4991 100644 --- a/src/TsTypes.ts +++ b/src/TsTypes.ts @@ -3,7 +3,6 @@ import { camelCase, upperFirst } from 'lodash' export namespace TsType { export interface TsTypeSettings { - addEnumUtils?: boolean declarationDescription?: boolean // TODO declareProperties?: boolean declareReferenced?: boolean @@ -18,7 +17,6 @@ export namespace TsType { } export var DEFAULT_SETTINGS: TsTypeSettings = { - addEnumUtils: false, declarationDescription: true, // declareProperties: false, declareReferenced: true, @@ -28,7 +26,7 @@ export namespace TsType { propertyDescription: true, useConstEnums: false, useFullReferencePathAsName: false, - useInterfaceDeclaration: true, + useInterfaceDeclaration: true } export abstract class TsTypeBase { diff --git a/src/index.ts b/src/index.ts index 0814e61b..e1571e9f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,3 @@ -import { EnumUtils } from './EnumUtils' import { JSONSchema } from './JSONSchema' import { format } from './pretty-printer' import { TsType } from './TsTypes' @@ -201,11 +200,6 @@ class Compiler { } else { retVal.id = path } - - if (this.settings.addEnumUtils){ - let utilPath = path + 'Utils' - this.declareType(new EnumUtils(enm), utilPath, utilPath) - } } return retVal diff --git a/test/cases/enum.ts b/test/cases/enum.ts index f445f11b..92fd6a65 100644 --- a/test/cases/enum.ts +++ b/test/cases/enum.ts @@ -39,50 +39,6 @@ export interface Enum { Two = 2, Three = 3 } -export interface Enum { - foo: "a" | "b" | "c"; - bar: Bar; -}` - }, - { - settings: { - addEnumUtils: true - }, - types: `export enum Bar { - One = 1, - Two = 2, - Three = 3 -} -export class BarUtil { - static values(): Bar[] { - return [Bar.One, Bar.Two, Bar.Three] - } - static toStringValue(enm: Bar): string { - switch (enm) { - case Bar.One: - return "one"; - case Bar.Two: - return "two"; - case Bar.Three: - return "three"; - } - } - static fromStringValue(value: string): Bar { - switch (value.toLowerCase()) { - case "one": - return Bar.One; - case "two": - return Bar.Two; - case "three": - return Bar.Three; - default: - throw new Error("Unrecognized Bar: " + value); - } - } - static fromStringValues(values: string[]): Bar[] { - return _.map(values, value => BarUtil.fromStringValue(value)); - } -} export interface Enum { foo: "a" | "b" | "c"; bar: Bar; From b8f50a319bc7cfa3cdb3bb2f230aa0faef26d51b Mon Sep 17 00:00:00 2001 From: Jonathan Ogilvie Date: Wed, 7 Sep 2016 14:40:42 -0400 Subject: [PATCH 21/27] Last round of code review feedback --- src/TsTypes.ts | 1 - src/index.ts | 19 +++++++++++++++---- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/TsTypes.ts b/src/TsTypes.ts index 101b4991..61090e3e 100644 --- a/src/TsTypes.ts +++ b/src/TsTypes.ts @@ -7,7 +7,6 @@ export namespace TsType { // TODO declareProperties?: boolean declareReferenced?: boolean declareSimpleType?: boolean - exportInterfaces?: boolean endPropertyWithSemicolon?: boolean endTypeWithSemicolon?: boolean propertyDescription?: boolean diff --git a/src/index.ts b/src/index.ts index e1571e9f..f08ebb43 100644 --- a/src/index.ts +++ b/src/index.ts @@ -109,7 +109,18 @@ class Compiler { if (refPath[0] !== '#'){ let id: string let fullPath = resolve(join(this.filePath.dir, refPath)) - let file = readFileSync(fullPath) + let file: Buffer + + if (fullPath.startsWith('http')) { + throw new ReferenceError('Remote http references are not yet supported. Could not read ' + fullPath) + } + + try { + file = readFileSync(fullPath) + } catch (err){ + throw new ReferenceError('Unable to find referenced file ' + fullPath) + } + let targetType = this.toTsType(JSON.parse(file.toString()), propName, false, true) if (targetType.id){ id = targetType.toSafeType(this.settings) @@ -257,10 +268,10 @@ class Compiler { throw TypeError('Enum was declared as an integer type, but found at least one non-integer member') } if (!rule.tsEnumNames){ - throw TypeError('Property tsEnumValues is required when enum is declared as an integer type') + throw TypeError('Property tsEnumNames is required when enum is declared as an integer type') } if (rule.tsEnumNames.length !== rule.enum!.length){ - throw TypeError('Property enum and property tsEnumValues must be the same length') + throw TypeError('Property enum and property tsEnumNames must be the same length') } throw TypeError('Enum was declared as an integer type, but found at least one non-string tsEnumValue') @@ -268,7 +279,7 @@ class Compiler { // I don't think we should ever hit this case. if (!isActuallyStringEnum && !isIntegerEnumWithValidStringValues){ - throw TypeError('Enum members must be a list of strings or a list of integers (with corresponding tsEnumValues)') + throw TypeError('Enum members must be a list of strings or a list of integers (with corresponding tsEnumNames)') } if (isIntegerEnumWithValidStringValues){ From 6eca6d0a42cd888295735ab2463731aaee049ef4 Mon Sep 17 00:00:00 2001 From: Jonathan Ogilvie Date: Wed, 7 Sep 2016 14:41:39 -0400 Subject: [PATCH 22/27] Fixing tests --- test/cases/enumValidation.3.ts | 2 +- test/cases/enumValidation.4.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/cases/enumValidation.3.ts b/test/cases/enumValidation.3.ts index 244bb978..b49a8855 100644 --- a/test/cases/enumValidation.3.ts +++ b/test/cases/enumValidation.3.ts @@ -17,5 +17,5 @@ export var settings = { export var error = { type: TypeError, - message: 'Property tsEnumValues is required when enum is declared as an integer type' + message: 'Property tsEnumNames is required when enum is declared as an integer type' } diff --git a/test/cases/enumValidation.4.ts b/test/cases/enumValidation.4.ts index 3e88b03d..1023f46f 100644 --- a/test/cases/enumValidation.4.ts +++ b/test/cases/enumValidation.4.ts @@ -18,5 +18,5 @@ export var settings = { export var error = { type: TypeError, - message: 'Property enum and property tsEnumValues must be the same length' + message: 'Property enum and property tsEnumNames must be the same length' } From 0895054331041a8026be472bdde428ea7bd75676 Mon Sep 17 00:00:00 2001 From: Jonathan Ogilvie Date: Wed, 7 Sep 2016 14:55:05 -0400 Subject: [PATCH 23/27] Updating dist --- dist/index.d.ts | 2 -- dist/index.js | 78 +++++++++++-------------------------------------- 2 files changed, 17 insertions(+), 63 deletions(-) diff --git a/dist/index.d.ts b/dist/index.d.ts index 2931fcb1..1925578d 100644 --- a/dist/index.d.ts +++ b/dist/index.d.ts @@ -57,11 +57,9 @@ export namespace JSONSchema { export namespace TsType { interface TsTypeSettings { - addEnumUtils?: boolean; declarationDescription?: boolean; declareReferenced?: boolean; declareSimpleType?: boolean; - exportInterfaces?: boolean; endPropertyWithSemicolon?: boolean; endTypeWithSemicolon?: boolean; propertyDescription?: boolean; diff --git a/dist/index.js b/dist/index.js index 18a524bc..f9279f27 100644 --- a/dist/index.js +++ b/dist/index.js @@ -5,58 +5,10 @@ var __extends = (this && this.__extends) || function (d, b) { function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; -var TsTypes = require('./TsTypes'); -var EnumUtils = (function (_super) { - __extends(EnumUtils, _super); - function EnumUtils(enm) { - _super.call(this); - this.enm = enm; - } - EnumUtils.prototype.isSimpleType = function () { return false; }; - EnumUtils.prototype._type = function (settings) { - // It's a bit hacky, but if this is a top level type, then addDeclaration changes - // our enum type's ID out from under us when it adds the enum to the declaration map, *after* - // the util class is declared. So we name ourselves by our enum's type, not our own ID' - return this.enm.toSafeType(settings) + "Util" || this.safeId() || 'SomeEnumTypeUtils'; - }; - EnumUtils.prototype.toSafeType = function (settings) { - return "" + this.toType(settings); - }; - EnumUtils.prototype.toDeclaration = function (settings) { - return this.toBlockComment(settings) + "export class " + this._type(settings) + " {\n " + this.makeValuesMethod(settings) + "\n " + this.makeToStringValueMethod(settings) + "\n " + this.makeFromStringValueMethod(settings) + "\n " + this.makeFromStringValuesMethod(settings) + "\n }"; - }; - EnumUtils.prototype.makeValuesMethod = function (settings) { - var enumType = this.enm.toSafeType(settings); - return "static values(): " + enumType + "[] {\n return [" + this.enm.enumValues.map(function (_) { return (enumType + "." + _.identifier); }).join(',') + "]\n }"; - }; - EnumUtils.prototype.makeFromStringValueMethod = function (settings) { - var enumType = this.enm.toSafeType(settings); - return "static fromStringValue(value: string): " + enumType + " {\n switch(value.toLowerCase()){\n " + this.enm.enumValues.map(function (_) { return ("case \"" + _.identifier.toLowerCase() + "\":\n return " + (enumType + '.' + _.identifier) + ";"); }).join('\n') + "\n default:\n throw new Error(\"Unrecognized " + enumType + ": \" + value);\n }\n }"; - }; - EnumUtils.prototype.makeToStringValueMethod = function (settings) { - var enumType = this.enm.toSafeType(settings); - return "static toStringValue(enm: " + enumType + "): string {\n switch(enm){\n " + this.enm.enumValues.map(function (_) { return ("case " + (enumType + '.' + _.identifier) + ":\n return \"" + _.identifier.toLowerCase() + "\";"); }).join('\n') + "\n }\n }"; - }; - EnumUtils.prototype.makeFromStringValuesMethod = function (settings) { - var enumType = this.enm.toSafeType(settings); - return "static fromStringValues(values: string[]): " + enumType + "[] {\n return _.map(values, value => " + this._type(settings) + ".fromStringValue(value));\n }"; - }; - return EnumUtils; -}(TsTypes.TsType.TsTypeBase)); -exports.EnumUtils = EnumUtils; - -},{"./TsTypes":2}],2:[function(require,module,exports){ -"use strict"; -var __extends = (this && this.__extends) || function (d, b) { - for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; - function __() { this.constructor = d; } - d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); -}; var lodash_1 = require('lodash'); var TsType; (function (TsType) { TsType.DEFAULT_SETTINGS = { - addEnumUtils: false, declarationDescription: true, // declareProperties: false, declareReferenced: true, @@ -66,7 +18,7 @@ var TsType; propertyDescription: true, useConstEnums: false, useFullReferencePathAsName: false, - useInterfaceDeclaration: true, + useInterfaceDeclaration: true }; var TsTypeBase = (function () { function TsTypeBase() { @@ -298,9 +250,8 @@ var TsType; TsType.Interface = Interface; })(TsType = exports.TsType || (exports.TsType = {})); -},{"lodash":undefined}],3:[function(require,module,exports){ +},{"lodash":undefined}],2:[function(require,module,exports){ "use strict"; -var EnumUtils_1 = require('./EnumUtils'); var pretty_printer_1 = require('./pretty-printer'); var TsTypes_1 = require('./TsTypes'); var fs_1 = require('fs'); @@ -403,7 +354,16 @@ var Compiler = (function () { if (refPath[0] !== '#') { var id = void 0; var fullPath = path_1.resolve(path_1.join(this.filePath.dir, refPath)); - var file = fs_1.readFileSync(fullPath); + var file = void 0; + if (fullPath.startsWith('http')) { + throw new ReferenceError('Remote http references are not yet supported. Could not read ' + fullPath); + } + try { + file = fs_1.readFileSync(fullPath); + } + catch (err) { + throw new ReferenceError('Unable to find referenced file ' + fullPath); + } var targetType = this.toTsType(JSON.parse(file.toString()), propName, false, true); if (targetType.id) { id = targetType.toSafeType(this.settings); @@ -485,10 +445,6 @@ var Compiler = (function () { else { retVal.id = path; } - if (this.settings.addEnumUtils) { - var utilPath = path + 'Utils'; - this.declareType(new EnumUtils_1.EnumUtils(enm), utilPath, utilPath); - } } return retVal; case EnumType.String: @@ -537,16 +493,16 @@ var Compiler = (function () { throw TypeError('Enum was declared as an integer type, but found at least one non-integer member'); } if (!rule.tsEnumNames) { - throw TypeError('Property tsEnumValues is required when enum is declared as an integer type'); + throw TypeError('Property tsEnumNames is required when enum is declared as an integer type'); } if (rule.tsEnumNames.length !== rule.enum.length) { - throw TypeError('Property enum and property tsEnumValues must be the same length'); + throw TypeError('Property enum and property tsEnumNames must be the same length'); } throw TypeError('Enum was declared as an integer type, but found at least one non-string tsEnumValue'); } // I don't think we should ever hit this case. if (!isActuallyStringEnum && !isIntegerEnumWithValidStringValues) { - throw TypeError('Enum members must be a list of strings or a list of integers (with corresponding tsEnumValues)'); + throw TypeError('Enum members must be a list of strings or a list of integers (with corresponding tsEnumNames)'); } if (isIntegerEnumWithValidStringValues) { return EnumType.Integer; @@ -622,7 +578,7 @@ function compileFromFile(inputFilename) { } exports.compileFromFile = compileFromFile; -},{"./EnumUtils":1,"./TsTypes":2,"./pretty-printer":4,"fs":undefined,"lodash":undefined,"path":undefined}],4:[function(require,module,exports){ +},{"./TsTypes":1,"./pretty-printer":3,"fs":undefined,"lodash":undefined,"path":undefined}],3:[function(require,module,exports){ // from https://github.com/Microsoft/TypeScript/wiki/Using-the-Compiler-API#pretty-printer-using-the-ls-formatter "use strict"; var ts = require('typescript'); @@ -674,5 +630,5 @@ function format(text) { } exports.format = format; -},{"typescript":undefined}]},{},[3])(3) +},{"typescript":undefined}]},{},[2])(2) }); \ No newline at end of file From 108147b67bbaf1eb3db149fdcc4a6013fa93cdf7 Mon Sep 17 00:00:00 2001 From: Jonathan Ogilvie Date: Wed, 7 Sep 2016 17:12:27 -0400 Subject: [PATCH 24/27] Default to const enums --- src/TsTypes.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/TsTypes.ts b/src/TsTypes.ts index 61090e3e..b4f311b3 100644 --- a/src/TsTypes.ts +++ b/src/TsTypes.ts @@ -23,7 +23,7 @@ export namespace TsType { endPropertyWithSemicolon: true, endTypeWithSemicolon: true, propertyDescription: true, - useConstEnums: false, + useConstEnums: true, useFullReferencePathAsName: false, useInterfaceDeclaration: true } From 392695f9f5e22c339e1a50987fd67be19dda96f7 Mon Sep 17 00:00:00 2001 From: Jonathan Ogilvie Date: Wed, 7 Sep 2016 17:14:15 -0400 Subject: [PATCH 25/27] Update dist --- dist/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dist/index.js b/dist/index.js index f9279f27..ce076b27 100644 --- a/dist/index.js +++ b/dist/index.js @@ -16,7 +16,7 @@ var TsType; endPropertyWithSemicolon: true, endTypeWithSemicolon: true, propertyDescription: true, - useConstEnums: false, + useConstEnums: true, useFullReferencePathAsName: false, useInterfaceDeclaration: true }; From 523eec758ef4a8d759c42e6cf6c673ce864b500a Mon Sep 17 00:00:00 2001 From: Jonathan Ogilvie Date: Thu, 8 Sep 2016 10:22:19 -0400 Subject: [PATCH 26/27] Remove propagation logic now that EnumValue is only used for int enums --- src/TsTypes.ts | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/TsTypes.ts b/src/TsTypes.ts index b4f311b3..fa4331c0 100644 --- a/src/TsTypes.ts +++ b/src/TsTypes.ts @@ -105,15 +105,8 @@ export namespace TsType { value: string constructor(enumValues: string[]) { - let hasValue = !!enumValues[0] - - // quirky propagation logic - if (hasValue){ - this.identifier = enumValues[0] - this.value = enumValues[1] - } else { - this.identifier = enumValues[1] - } + this.identifier = enumValues[0] + this.value = enumValues[1] } toDeclaration(){ From d3abd8c63ef31828bb0f1383b56113c3dd4e367b Mon Sep 17 00:00:00 2001 From: Jonathan Ogilvie Date: Thu, 8 Sep 2016 10:22:36 -0400 Subject: [PATCH 27/27] Dist --- dist/index.js | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/dist/index.js b/dist/index.js index ce076b27..b4e11b5a 100644 --- a/dist/index.js +++ b/dist/index.js @@ -128,15 +128,8 @@ var TsType; TsType.Literal = Literal; var EnumValue = (function () { function EnumValue(enumValues) { - var hasValue = !!enumValues[0]; - // quirky propagation logic - if (hasValue) { - this.identifier = enumValues[0]; - this.value = enumValues[1]; - } - else { - this.identifier = enumValues[1]; - } + this.identifier = enumValues[0]; + this.value = enumValues[1]; } EnumValue.prototype.toDeclaration = function () { // if there is a value associated with the identifier, declare as identifier=value