stringify.js 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277
  1. /* eslint-disable class-methods-use-this */
  2. const Types = require('./types');
  3. function combineNameAndType(nameString, typeString) {
  4. const separator = (nameString && typeString) ? ':' : '';
  5. return nameString + separator + typeString;
  6. }
  7. class Stringifier {
  8. constructor(options) {
  9. this._options = options || {};
  10. this._options.linkClass = this._options.linkClass || this._options.cssClass;
  11. }
  12. applications(applications) {
  13. let result = '';
  14. const strings = [];
  15. if (!applications) {
  16. return result;
  17. }
  18. for (let i = 0, l = applications.length; i < l; i++) {
  19. strings.push(this.type(applications[i]));
  20. }
  21. if (this._options.htmlSafe) {
  22. result = '.&lt;';
  23. } else {
  24. result = '.<';
  25. }
  26. result += `${strings.join(', ')}>`;
  27. return result;
  28. }
  29. elements(elements) {
  30. let result = '';
  31. const strings = [];
  32. if (!elements) {
  33. return result;
  34. }
  35. for (let i = 0, l = elements.length; i < l; i++) {
  36. strings.push(this.type(elements[i]));
  37. }
  38. result = `(${strings.join('|')})`;
  39. return result;
  40. }
  41. key(type) {
  42. return this.type(type);
  43. }
  44. name(name) {
  45. return name || '';
  46. }
  47. new(funcNew) {
  48. return funcNew ? `new:${this.type(funcNew)}` : '';
  49. }
  50. nullable(nullable) {
  51. switch (nullable) {
  52. case true:
  53. return '?';
  54. case false:
  55. return '!';
  56. default:
  57. return '';
  58. }
  59. }
  60. optional(optional) {
  61. if (optional === true) {
  62. return '=';
  63. } else {
  64. return '';
  65. }
  66. }
  67. params(params) {
  68. let result = '';
  69. const strings = [];
  70. if (!params || params.length === 0) {
  71. return result;
  72. }
  73. for (let i = 0, l = params.length; i < l; i++) {
  74. strings.push(this.type(params[i]));
  75. }
  76. result = strings.join(', ');
  77. return result;
  78. }
  79. result(result) {
  80. return result ? `: ${this.type(result)}` : '';
  81. }
  82. stringify(type) {
  83. return this.type(type);
  84. }
  85. this(funcThis) {
  86. return funcThis ? `this:${this.type(funcThis)}` : '';
  87. }
  88. type(type) {
  89. let typeString = '';
  90. if (!type) {
  91. return typeString;
  92. }
  93. switch (type.type) {
  94. case Types.AllLiteral:
  95. typeString = this._formatNameAndType(type, '*');
  96. break;
  97. case Types.FunctionType:
  98. typeString = this._signature(type);
  99. break;
  100. case Types.NullLiteral:
  101. typeString = this._formatNameAndType(type, 'null');
  102. break;
  103. case Types.RecordType:
  104. typeString = this._record(type);
  105. break;
  106. case Types.TypeApplication:
  107. typeString = this.type(type.expression) + this.applications(type.applications);
  108. break;
  109. case Types.UndefinedLiteral:
  110. typeString = this._formatNameAndType(type, 'undefined');
  111. break;
  112. case Types.TypeUnion:
  113. typeString = this.elements(type.elements);
  114. break;
  115. case Types.UnknownLiteral:
  116. typeString = this._formatNameAndType(type, '?');
  117. break;
  118. default:
  119. typeString = this._formatNameAndType(type);
  120. }
  121. // add optional/nullable/repeatable modifiers
  122. if (!this._options._ignoreModifiers) {
  123. typeString = this._addModifiers(type, typeString);
  124. }
  125. return typeString;
  126. }
  127. _record(type) {
  128. const fields = this._recordFields(type.fields);
  129. return `{${fields.join(', ')}}`;
  130. }
  131. _recordFields(fields) {
  132. let field;
  133. let keyAndValue;
  134. const result = [];
  135. if (!fields) {
  136. return result;
  137. }
  138. for (let i = 0, l = fields.length; i < l; i++) {
  139. field = fields[i];
  140. keyAndValue = this.key(field.key);
  141. keyAndValue += field.value ? `: ${this.type(field.value)}` : '';
  142. result.push(keyAndValue);
  143. }
  144. return result;
  145. }
  146. // Adds optional, nullable, and repeatable modifiers if necessary.
  147. _addModifiers(type, typeString) {
  148. let combined;
  149. let optional = '';
  150. let repeatable = '';
  151. if (type.repeatable) {
  152. repeatable = '...';
  153. }
  154. combined = this.nullable(type.nullable) + combineNameAndType('', typeString);
  155. optional = this.optional(type.optional);
  156. return repeatable + combined + optional;
  157. }
  158. _addLinks(nameString) {
  159. const href = this._getHrefForString(nameString);
  160. let link = nameString;
  161. let linkClass = this._options.linkClass || '';
  162. if (href) {
  163. if (linkClass) {
  164. linkClass = ` class="${linkClass}"`;
  165. }
  166. link = `<a href="${href}"${linkClass}>${nameString}</a>`;
  167. }
  168. return link;
  169. }
  170. _formatNameAndType(type, literal) {
  171. let nameString = type.name || literal || '';
  172. const typeString = type.type ? this.type(type.type) : '';
  173. nameString = this._addLinks(nameString);
  174. return combineNameAndType(nameString, typeString);
  175. }
  176. _getHrefForString(nameString) {
  177. let href = '';
  178. const links = this._options.links;
  179. if (!links) {
  180. return href;
  181. }
  182. // accept a map or an object
  183. if (links instanceof Map) {
  184. href = links.get(nameString);
  185. } else if ({}.hasOwnProperty.call(links, nameString)) {
  186. href = links[nameString];
  187. }
  188. return href;
  189. }
  190. _signature(type) {
  191. let param;
  192. let prop;
  193. let signature;
  194. const params = [];
  195. // these go within the signature's parens, in this order
  196. const props = [
  197. 'new',
  198. 'this',
  199. 'params'
  200. ];
  201. for (let i = 0, l = props.length; i < l; i++) {
  202. prop = props[i];
  203. param = this[prop](type[prop]);
  204. if (param.length > 0) {
  205. params.push(param);
  206. }
  207. }
  208. signature = `function(${params.join(', ')})`;
  209. signature += this.result(type.result);
  210. return signature;
  211. }
  212. }
  213. module.exports = (type, options) => new Stringifier(options).stringify(type);