Skip to content
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

Allow to have variable name containing dashes in them #40

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
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
103 changes: 55 additions & 48 deletions lib/typson-schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,16 @@ var TypeScript, _, traverse; // from global scope
if (underscore) {
_ = underscore;
}
if( _traverse) {
if (_traverse) {
traverse = _traverse;
}
var api = {};
var primitiveTypes = [ "string", "number", "boolean", "any" ];
var validationKeywords = [ "type", "minimum", "exclusiveMinimum", "maximum", "exclusiveMaximum", "multipleOf", "minLength", "maxLength", "format", "pattern", "minItems", "maxItems", "uniqueItems", "default", "additionalProperties" ];
var primitiveTypes = ["string", "number", "boolean", "any"];
var validationKeywords = ["type", "minimum", "exclusiveMinimum", "maximum", "exclusiveMaximum", "multipleOf", "minLength", "maxLength", "format", "pattern", "minItems", "maxItems", "uniqueItems", "default", "additionalProperties"];
var annotedValidationKeywordPattern = /@[a-z.]+\s*[^@\s]+/gi;
var TypescriptASTFlags = { "optionalName" : 4, "arrayType" : 8 };
var defaultProperties = { additionalProperties: false};
var variableWithDash = /^'([A-z0-9]+-[A-z0-9]+)'$/gi;
var TypescriptASTFlags = {"optionalName": 4, "arrayType": 8};
var defaultProperties = {additionalProperties: false};

/**
* Creates json-schema type definitions from a type script.
Expand All @@ -49,20 +50,20 @@ var TypeScript, _, traverse; // from global scope
typson.tree(script).done(function (tree) {
TypeScript = typson.TypeScript;
var definitions = {
interfaces: {},
enums: {}
};
interfaces: {},
enums: {}
};
_.each(tree, function (script) {
_.each(script.moduleElements.members, function (decl) {
handleDeclaration(decl, definitions, refPath, "");
});
});

traverse(definitions.interfaces).forEach(function(item) {
if(this.key === "$ref") {
traverse(definitions.interfaces).forEach(function (item) {
if (this.key === "$ref") {
var currentSchema = this.path[0];
var lastDot = currentSchema.lastIndexOf(".");
if(lastDot > -1) {
if (lastDot > -1) {
this.update(fullModulePath(currentSchema.substring(0, lastDot), item, definitions.interfaces));
}
}
Expand All @@ -82,12 +83,12 @@ var TypeScript, _, traverse; // from global scope
*/
api.schema = function (script, type, id) {
return Q.promise(function (resolve) {
api.definitions(script, "#/definitions").done(function(definitions) {
api.definitions(script, "#/definitions").done(function (definitions) {
var schema = {};
schema.$schema = "http://json-schema.org/draft-04/schema#";
_.extend(schema, definitions[type]);
delete definitions[type];
if(id) {
if (id) {
schema.id = id;
}
resolve(schema);
Expand All @@ -98,7 +99,7 @@ var TypeScript, _, traverse; // from global scope

function handleDeclaration(decl, definitions, refPath, modulePath) {
if (decl.nodeType() === TypeScript.NodeType.InterfaceDeclaration ||
decl.nodeType() === TypeScript.NodeType.ClassDeclaration) {
decl.nodeType() === TypeScript.NodeType.ClassDeclaration) {
handleInterfaceDeclaration(decl, definitions, refPath, modulePath);
}
else if (decl.nodeType() === TypeScript.NodeType.ModuleDeclaration) {
Expand All @@ -107,8 +108,8 @@ var TypeScript, _, traverse; // from global scope
} else {
_.each(decl.members.members, function (subDecl) {
var subModulePath = modulePath;
if(!(decl.getModuleFlags() & TypeScript.ModuleFlags.IsWholeFile)) {
subModulePath+=(decl.name.actualText+".");
if (!(decl.getModuleFlags() & TypeScript.ModuleFlags.IsWholeFile)) {
subModulePath += (decl.name.actualText + ".");
}
handleDeclaration(subDecl, definitions, refPath, subModulePath);
});
Expand All @@ -128,7 +129,7 @@ var TypeScript, _, traverse; // from global scope
definition.id = name;
definition.type = "object";
copyComment(type, definition);

definition.properties = {};
mergeInheritedProperties(type, definition, definitions);
handlePropertyDeclaration(type, definition, definitions, refPath);
Expand All @@ -137,8 +138,8 @@ var TypeScript, _, traverse; // from global scope


/**
* Handles property or interface declarations and adds them to the interface definition.
* Recursive, if the property has child properties, i.e. inline object declarations,
* Handles property or interface declarations and adds them to the interface definition.
* Recursive, if the property has child properties, i.e. inline object declarations,
* this function will recurse down to get all the definitions in the schema.
*
* @param type {object} the TypeScript AST node associated to the property declaration
Expand All @@ -147,7 +148,12 @@ var TypeScript, _, traverse; // from global scope
*/
function handlePropertyDeclaration(type, definition, definitions, refPath) {
_.each(type.members.members, function (variable) {
var property = definition.properties[variable.id.actualText] = {};
var variableName = variable.id.actualText;
var testPath = variableWithDash.exec(variableName);
if(testPath) {
variableName = testPath[1];
}
var property = definition.properties[variableName] = {};
copyComment(variable, property);
var overridenType = property.type;
var variableType = extractFullTypeName(variable.typeExpr.term);
Expand All @@ -158,7 +164,7 @@ var TypeScript, _, traverse; // from global scope
if (!definition.required) {
definition.required = [];
}
definition.required.push(variable.id.actualText);
definition.required.push(variableName);
}
//arrays
if (variable.typeExpr.getFlags() & TypescriptASTFlags.arrayType) {
Expand Down Expand Up @@ -193,7 +199,7 @@ var TypeScript, _, traverse; // from global scope
handlePropertyDeclaration(variable.typeExpr.term, property, definitions);
variableType = "any";
}
}
}
//other
else {
propertyType = property;
Expand All @@ -206,33 +212,32 @@ var TypeScript, _, traverse; // from global scope
propertyType.id = definitions.enums[fullTypeName].id;
propertyType.enum = _.keys(definitions.enums[fullTypeName].enumeration);
addEnumDescription(definitions.enums[fullTypeName].enumeration, property);
}
}
//other
else if (primitiveTypes.indexOf(variableType) === -1) {
propertyType.$ref = refPath? refPath+"/"+fullTypeName: fullTypeName;
propertyType.$ref = refPath ? refPath + "/" + fullTypeName : fullTypeName;
} else {
if(variableType !== "any") {
if (variableType !== "any") {
propertyType.type = overridenType || variableType;
}
}
});
}

function extractFullTypeName(term) {

if(term.actualText) {
if (term.actualText) {
return term.actualText;
} else {
if(term.nodeType() === TypeScript.NodeType.MemberAccessExpression) {
return extractFullTypeName(term.operand1)+"."+extractFullTypeName(term.operand2);
if (term.nodeType() === TypeScript.NodeType.MemberAccessExpression) {
return extractFullTypeName(term.operand1) + "." + extractFullTypeName(term.operand2);
}
}
}

/**
* Visits every super type extended by the given type recursively and provisions the given definition with the properties of the associated super definitions.
*
* @param type {object} the TypeScript AST node associated to the interface declaration
*
* @param type {object} the TypeScript AST node associated to the interface declaration
* @param definition {object} the definition to be provisionned
* @param definitions {object} the set of handled interface and enum definitions
*/
Expand All @@ -245,7 +250,7 @@ var TypeScript, _, traverse; // from global scope
// recursive call
mergeInheritedProperties(superType, definition, definitions);
// merges properties
for(var superKey in superDefinition.properties) {
for (var superKey in superDefinition.properties) {
definition.properties[superKey] = superDefinition.properties[superKey];
}
// merges required
Expand All @@ -259,9 +264,9 @@ var TypeScript, _, traverse; // from global scope
}
}
});
}
}
}

/**
* Handles enum declaration as new definition and registers it in the global set of enum definitions
*
Expand Down Expand Up @@ -356,14 +361,16 @@ var TypeScript, _, traverse; // from global scope


function addEnumDescription(enumeration, property) {
if(enumeration && _.values(enumeration).join("")) {
if (enumeration && _.values(enumeration).join("")) {
var values = ["Value"];
values = values.concat(_.keys(enumeration));
var max = _.max(values, function(value) { return value.length; }).length;
var table = "\n\n| "+values[0] + new Array(max+3-values[0].length).join(" ") + "| Description";
var max = _.max(values, function (value) {
return value.length;
}).length;
var table = "\n\n| " + values[0] + new Array(max + 3 - values[0].length).join(" ") + "| Description";
table += "\n|-";
_.each(enumeration, function(comment, value) {
table += "\n| `"+value+"`"+ new Array(max+1-value.length).join(" ") + "|" + (comment ? " "+comment : "|");
_.each(enumeration, function (comment, value) {
table += "\n| `" + value + "`" + new Array(max + 1 - value.length).join(" ") + "|" + (comment ? " " + comment : "|");
});
property.description = property.description || "";
property.description += table;
Expand All @@ -381,27 +388,27 @@ var TypeScript, _, traverse; // from global scope
*/
function fullModulePath(refModule, target, types) {
var path = target.split("/");
var prefix = path.splice(0, path.length-1).join("/");
var prefix = path.splice(0, path.length - 1).join("/");
target = path.splice(-1)[0];
var refPath = refModule.split(".");
while(refPath.length > 0) {
var candidate = refPath.join(".")+"."+target;
if(candidate in types) {
while (refPath.length > 0) {
var candidate = refPath.join(".") + "." + target;
if (candidate in types) {
target = candidate;
break;
}
refPath = refPath.slice(0, refPath.length-1);
refPath = refPath.slice(0, refPath.length - 1);
}
return prefix.length > 0 ? prefix + "/" + target : target;
}

api.exec = function(script, type) {
if(type) {
api.schema(script, type).done(function(schema) {
api.exec = function (script, type) {
if (type) {
api.schema(script, type).done(function (schema) {
console.log(JSON.stringify(schema, null, 2));
});
} else {
api.definitions(script).done(function(definitions) {
api.definitions(script).done(function (definitions) {
console.log(JSON.stringify(definitions, null, 2));
});
}
Expand Down