Skip to content

Add ts enums #15

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 30 commits into from
Sep 8, 2016
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't check this in, I don't use this setup.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This setup doesn't use anything environment-specific. It's generalizable so long as we use mocha, so if someone writes TS in vscode, this run configuration can save them the work of figuring out how to debug tests. I think it adds value, but obviously it's your project so I'll remove it if you still think I should.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see what you mean. Also I realized that .vscode/settings is already checked in - let's keep this guy here :)

"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"}
}
]
}
33 changes: 32 additions & 1 deletion dist/index.d.ts
Original file line number Diff line number Diff line change
@@ -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<string | Error>;

export namespace JSONSchema {
Expand Down Expand Up @@ -42,6 +42,7 @@ export namespace JSONSchema {
[k: string]: HttpJsonSchemaOrgDraft04Schema | string[];
};
enum?: any[];
tsEnumNames?: string[];
type?: SimpleTypes | SimpleTypes[];
allOf?: HttpJsonSchemaOrgDraft04Schema[];
anyOf?: HttpJsonSchemaOrgDraft04Schema[];
Expand All @@ -60,10 +61,13 @@ export namespace TsType {
declareReferenced?: boolean;
useFullReferencePathAsName?: boolean;
useInterfaceDeclaration?: boolean;
useTypescriptEnums?: boolean;
exportInterfaces?: boolean;
endTypeWithSemicolon?: boolean;
endPropertyWithSemicolon?: boolean;
declarationDescription?: boolean;
propertyDescription?: boolean;
addEnumUtils?: boolean;
}
var DEFAULT_SETTINGS: TsTypeSettings;
abstract class TsType {
Expand Down Expand Up @@ -106,6 +110,33 @@ export namespace TsType {
constructor(value: any);
_type(): any;
}
class EnumValue {
identifier: string;
value: string;
constructor(enumValues: string[]);
toDeclaration(): string;
toString(): string;
}
class Enum extends TsType {
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;
Expand Down
195 changes: 169 additions & 26 deletions dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@ var TsType;
propertyDescription: true,
useFullReferencePathAsName: false,
// declareProperties: false,
useInterfaceDeclaration: true
useInterfaceDeclaration: true,
useTypescriptEnums: false,
exportInterfaces: false,
addEnumUtils: false
};
var TsType = (function () {
function TsType() {
Expand Down Expand Up @@ -125,6 +128,86 @@ var TsType;
return Literal;
}(TsType));
TsType_1.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];
}
}
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(enumValues) {
_super.call(this);
this.enumValues = enumValues;
}
Enum.prototype.isSimpleType = function () { return false; };
Enum.prototype._type = function (settings) {
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 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 + "): 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;
var Array = (function (_super) {
__extends(Array, _super);
function Array(type) {
Expand Down Expand Up @@ -198,12 +281,10 @@ 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);
}
else {
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 Interface;
}(TsType));
Expand All @@ -215,6 +296,7 @@ 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 RuleType;
(function (RuleType) {
Expand All @@ -235,12 +317,14 @@ 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);
this.declareType(this.toTsType(this.schema), this.id, this.id);
this.declareType(this.toTsType(this.schema, '', true), this.id, this.id);
}
Compiler.prototype.toString = function () {
var _this = this;
Expand All @@ -258,6 +342,7 @@ var Compiler = (function () {
if (rule.type === 'array' && rule.items) {
return RuleType.TypedArray;
}
// enum type vs enum constant?
if (rule.enum) {
return RuleType.Enum;
}
Expand Down Expand Up @@ -297,12 +382,31 @@ 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 === '#/')
// 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);
var parts = path.slice(2).split('/');
}
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) {
id = targetType.toSafeType(this.settings);
}
else {
var parsedNewFile = path_1.parse(fullPath);
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('/');
var ret = this.settings.declareReferenced ? this.declarations.get(parts.join('/')) : undefined;
if (!ret) {
var cur = this.schema;
Expand All @@ -315,9 +419,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) {
Expand All @@ -335,14 +439,51 @@ 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:
return new TsTypes_1.TsType.Union(lodash_1.uniqBy(rule.enum.map(function (_) { return _this.toStringLiteral(_); }), function (_) { return _.toType(_this.settings); }));
// 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) {
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 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); }));
}
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));
Expand All @@ -357,12 +498,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 new Error('Unknown rule:' + rule.toString());
};
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;
Expand All @@ -374,7 +517,7 @@ var Compiler = (function () {
return {
name: k,
required: _this.isRequired(k, copy),
type: _this.toTsType(v)
type: _this.toTsType(v, k),
};
});
if (props.length === 0 && !('additionalProperties' in schema)) {
Expand Down Expand Up @@ -403,8 +546,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) {
Expand All @@ -414,14 +557,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');
Expand Down
Loading