forked from mirrors/easyappointments
258 lines
5.6 KiB
JavaScript
258 lines
5.6 KiB
JavaScript
|
'use strict';
|
||
|
|
||
|
var Types = require('./types');
|
||
|
|
||
|
function Stringifier(options) {
|
||
|
this._options = options || {};
|
||
|
|
||
|
// in a list of function signature params, repeatable params are stringified differently
|
||
|
this._inFunctionSignatureParams = false;
|
||
|
}
|
||
|
|
||
|
Stringifier.prototype.applications = function(applications) {
|
||
|
if (!applications) {
|
||
|
return '';
|
||
|
}
|
||
|
|
||
|
var parsedApplications = [];
|
||
|
var result = '';
|
||
|
|
||
|
for (var i = 0, l = applications.length; i < l; i++) {
|
||
|
parsedApplications.push(this.type(applications[i]));
|
||
|
}
|
||
|
|
||
|
if (this._options.htmlSafe) {
|
||
|
result = '.<';
|
||
|
} else {
|
||
|
result = '.<';
|
||
|
}
|
||
|
|
||
|
result += parsedApplications.join(', ') + '>';
|
||
|
|
||
|
return result;
|
||
|
};
|
||
|
|
||
|
Stringifier.prototype.elements = function(elements) {
|
||
|
if (!elements) {
|
||
|
return '';
|
||
|
}
|
||
|
|
||
|
var result = [];
|
||
|
|
||
|
for (var i = 0, l = elements.length; i < l; i++) {
|
||
|
result.push(this.type(elements[i]));
|
||
|
}
|
||
|
|
||
|
return '(' + result.join('|') + ')';
|
||
|
};
|
||
|
|
||
|
Stringifier.prototype.name = function(name) {
|
||
|
return name || '';
|
||
|
};
|
||
|
|
||
|
Stringifier.prototype['new'] = function(funcNew) {
|
||
|
return funcNew ? 'new:' + this.type(funcNew) : '';
|
||
|
};
|
||
|
|
||
|
Stringifier.prototype.nullable = function(nullable) {
|
||
|
switch (nullable) {
|
||
|
case true:
|
||
|
return '?';
|
||
|
case false:
|
||
|
return '!';
|
||
|
default:
|
||
|
return '';
|
||
|
}
|
||
|
};
|
||
|
|
||
|
Stringifier.prototype.optional = function(optional) {
|
||
|
/*jshint boss: true */ // TODO: remove after JSHint releases the fix for jshint/jshint#878
|
||
|
if (optional === true) {
|
||
|
return '=';
|
||
|
} else {
|
||
|
return '';
|
||
|
}
|
||
|
};
|
||
|
|
||
|
Stringifier.prototype.params = function(params) {
|
||
|
if (!params || params.length === 0) {
|
||
|
return '';
|
||
|
}
|
||
|
|
||
|
var result = [];
|
||
|
|
||
|
var param;
|
||
|
for (var i = 0, l = params.length; i < l; i++) {
|
||
|
result.push(this.type(params[i]));
|
||
|
}
|
||
|
|
||
|
return result.join(', ');
|
||
|
};
|
||
|
|
||
|
Stringifier.prototype.properties = function(props) {
|
||
|
if (!props) {
|
||
|
return '';
|
||
|
}
|
||
|
|
||
|
var result = [];
|
||
|
|
||
|
for (var i = 0, l = props.length; i < l; i++) {
|
||
|
result.push(this._formatNameAndType(props[i].name, props[i].type));
|
||
|
}
|
||
|
|
||
|
return result;
|
||
|
};
|
||
|
|
||
|
Stringifier.prototype.result = function(result) {
|
||
|
return result ? ': ' + this.type(result) : '';
|
||
|
};
|
||
|
|
||
|
Stringifier.prototype['this'] = function(funcThis) {
|
||
|
return funcThis ? 'this:' + this.type(funcThis) : '';
|
||
|
};
|
||
|
|
||
|
Stringifier.prototype.type = function(type) {
|
||
|
if (!type) {
|
||
|
return '';
|
||
|
}
|
||
|
|
||
|
// nullable comes first
|
||
|
var result = this.nullable(type.nullable);
|
||
|
|
||
|
// next portion varies by type
|
||
|
switch(type.type) {
|
||
|
case Types.AllLiteral:
|
||
|
result += this._formatNameAndType(type, '*');
|
||
|
break;
|
||
|
case Types.FunctionType:
|
||
|
result += this._signature(type);
|
||
|
break;
|
||
|
case Types.NullLiteral:
|
||
|
result += this._formatNameAndType(type, 'null');
|
||
|
break;
|
||
|
case Types.RecordType:
|
||
|
result += this._record(type);
|
||
|
break;
|
||
|
case Types.TypeApplication:
|
||
|
result += this.type(type.expression);
|
||
|
result += this.applications(type.applications);
|
||
|
break;
|
||
|
case Types.UndefinedLiteral:
|
||
|
result += this._formatNameAndType(type, 'undefined');
|
||
|
break;
|
||
|
case Types.TypeUnion:
|
||
|
result += this.elements(type.elements);
|
||
|
break;
|
||
|
case Types.UnknownLiteral:
|
||
|
result += this._formatNameAndType(type, '?');
|
||
|
break;
|
||
|
default:
|
||
|
result += this._formatNameAndType(type);
|
||
|
}
|
||
|
|
||
|
// finally, optionality
|
||
|
result += this.optional(type.optional);
|
||
|
|
||
|
return result;
|
||
|
};
|
||
|
|
||
|
Stringifier.prototype.stringify = Stringifier.prototype.type;
|
||
|
|
||
|
Stringifier.prototype.key = Stringifier.prototype.type;
|
||
|
|
||
|
Stringifier.prototype._record = function(type) {
|
||
|
var fields = this._recordFields(type.fields);
|
||
|
|
||
|
return '{' + fields.join(', ') + '}';
|
||
|
};
|
||
|
|
||
|
Stringifier.prototype._recordFields = function(fields) {
|
||
|
if (!fields) {
|
||
|
return '';
|
||
|
}
|
||
|
|
||
|
var result = [];
|
||
|
|
||
|
var field;
|
||
|
var keyAndValue;
|
||
|
|
||
|
for (var i = 0, l = fields.length; i < l; i++) {
|
||
|
field = fields[i];
|
||
|
|
||
|
keyAndValue = this.key(field.key);
|
||
|
keyAndValue += field.value ? ': ' + this.type(field.value) : '';
|
||
|
|
||
|
result.push(keyAndValue);
|
||
|
}
|
||
|
|
||
|
return result;
|
||
|
};
|
||
|
|
||
|
function combineNameAndType(nameString, typeString) {
|
||
|
var separator = (nameString && typeString) ? ':' : '';
|
||
|
return nameString + separator + typeString;
|
||
|
}
|
||
|
|
||
|
Stringifier.prototype._formatRepeatable = function(nameString, typeString) {
|
||
|
var open = this._inFunctionSignatureParams ? '...[' : '...';
|
||
|
var close = this._inFunctionSignatureParams ? ']' : '';
|
||
|
|
||
|
return open + combineNameAndType(nameString, typeString) + close;
|
||
|
};
|
||
|
|
||
|
Stringifier.prototype._formatNameAndType = function(type, literal) {
|
||
|
var nameString = type.name || literal || '';
|
||
|
var typeString = type.type ? this.type(type.type) : '';
|
||
|
var cssClass;
|
||
|
var openTag;
|
||
|
|
||
|
// replace the type with an HTML link if necessary
|
||
|
if (this._options.links && Object.prototype.hasOwnProperty.call(this._options.links,
|
||
|
nameString)) {
|
||
|
cssClass = this._options.cssClass ? ' class="' + this._options.cssClass + '"' : '';
|
||
|
|
||
|
openTag = '<a href="' + this._options.links[nameString] + '"' + cssClass + '>';
|
||
|
nameString = openTag + nameString + '</a>';
|
||
|
}
|
||
|
|
||
|
if (type.repeatable === true) {
|
||
|
return this._formatRepeatable(nameString, typeString);
|
||
|
} else {
|
||
|
return combineNameAndType(nameString, typeString);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
Stringifier.prototype._signature = function(type) {
|
||
|
var params = [];
|
||
|
var param;
|
||
|
var result;
|
||
|
|
||
|
// these go within the signature's parens, in this order
|
||
|
var props = [
|
||
|
'new',
|
||
|
'this',
|
||
|
'params'
|
||
|
];
|
||
|
var prop;
|
||
|
|
||
|
this._inFunctionSignatureParams = true;
|
||
|
for (var i = 0, l = props.length; i < l; i++) {
|
||
|
prop = props[i];
|
||
|
param = this[prop](type[prop]);
|
||
|
if (param.length > 0) {
|
||
|
params.push(param);
|
||
|
}
|
||
|
}
|
||
|
this._inFunctionSignatureParams = false;
|
||
|
|
||
|
result = 'function(' + params.join(', ') + ')';
|
||
|
result += this.result(type.result);
|
||
|
|
||
|
return result;
|
||
|
};
|
||
|
|
||
|
|
||
|
module.exports = function(type, options) {
|
||
|
return new Stringifier(options).stringify(type);
|
||
|
};
|