├── .gitignore ├── .travis.yml ├── _.js ├── __tests__ ├── __snapshots__ │ └── fixtures.js.snap ├── array-mixed.js ├── array-numbers.js ├── array-objs-camel.js ├── array-objs.js ├── boolean.js ├── chrome │ ├── chrome.json │ └── out.d.ts ├── depth-01.js ├── depth-02.js ├── dup-02.js ├── dup-members-propname-mismatch.js ├── dup-members.js ├── dup-merge.js ├── dup.js ├── fixtures.js ├── flow.js ├── gmaps │ ├── in.json │ └── out.d.ts ├── invalid-interface-name.js ├── invalid-propname.js ├── large-01.js ├── magento │ ├── categories-out.d.ts │ ├── categories.json │ ├── out.d.ts │ └── product.json ├── missing-props.js ├── multi-complex.js ├── multi-missing.js ├── multi-union.js ├── multi │ ├── 01.json │ └── 02.json ├── namespace.js ├── null.js ├── numbers.js ├── petition │ ├── input.json │ └── output.d.ts ├── prefix-empty.js ├── prefix.js ├── rootName.js ├── swagger │ ├── schema.d.ts │ └── schema.json ├── top-level-array-mixed.js ├── top-level-array-objects.js ├── top-level-array.js ├── top-level-multi-arrays.js └── top-level-string.js ├── crossbow.yml ├── docs ├── css │ └── styles.css ├── dist │ ├── codemirror.js │ ├── index.js │ ├── javascript.js │ └── json-ts.min.js ├── index.html ├── javascript │ ├── index.html │ ├── javascript.js │ ├── json-ld.html │ └── typescript.html ├── json │ ├── invalid-keys.json │ ├── nested.json │ ├── optional.json │ └── recursive.json ├── lib │ ├── codemirror.css │ └── codemirror.js ├── package.json ├── src │ └── index.js └── yarn.lock ├── json-ts2.gif ├── package.json ├── readme.md ├── src ├── bin.ts ├── collapse-interfaces.ts ├── index.ts ├── parser.ts ├── printer.ts ├── transformer.ts └── util.ts ├── tsconfig.json └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | node_modules 3 | parser.js 4 | printer.js 5 | transformer.js 6 | example.js 7 | src/*.js 8 | _.min.js 9 | /dist/** 10 | .crossbow/** 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | git: 3 | depth: 2 4 | language: node_js 5 | node_js: 6 | - "8" 7 | - "6" -------------------------------------------------------------------------------- /_.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Lodash (Custom Build) 4 | * Build: `lodash include="startCase,toLower" -o _.js` 5 | * Copyright JS Foundation and other contributors 6 | * Released under MIT license 7 | * Based on Underscore.js 1.8.3 8 | * Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors 9 | */ 10 | ;(function() { 11 | 12 | /** Used as a safe reference for `undefined` in pre-ES5 environments. */ 13 | var undefined; 14 | 15 | /** Used as the semantic version number. */ 16 | var VERSION = '4.17.4'; 17 | 18 | /** Used as references for various `Number` constants. */ 19 | var INFINITY = 1 / 0; 20 | 21 | /** `Object#toString` result references. */ 22 | var nullTag = '[object Null]', 23 | symbolTag = '[object Symbol]', 24 | undefinedTag = '[object Undefined]'; 25 | 26 | /** Used to match words composed of alphanumeric characters. */ 27 | var reAsciiWord = /[^\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f]+/g; 28 | 29 | /** Used to match Latin Unicode letters (excluding mathematical operators). */ 30 | var reLatin = /[\xc0-\xd6\xd8-\xf6\xf8-\xff\u0100-\u017f]/g; 31 | 32 | /** Used to compose unicode character classes. */ 33 | var rsAstralRange = '\\ud800-\\udfff', 34 | rsComboMarksRange = '\\u0300-\\u036f', 35 | reComboHalfMarksRange = '\\ufe20-\\ufe2f', 36 | rsComboSymbolsRange = '\\u20d0-\\u20ff', 37 | rsComboRange = rsComboMarksRange + reComboHalfMarksRange + rsComboSymbolsRange, 38 | rsDingbatRange = '\\u2700-\\u27bf', 39 | rsLowerRange = 'a-z\\xdf-\\xf6\\xf8-\\xff', 40 | rsMathOpRange = '\\xac\\xb1\\xd7\\xf7', 41 | rsNonCharRange = '\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf', 42 | rsPunctuationRange = '\\u2000-\\u206f', 43 | rsSpaceRange = ' \\t\\x0b\\f\\xa0\\ufeff\\n\\r\\u2028\\u2029\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000', 44 | rsUpperRange = 'A-Z\\xc0-\\xd6\\xd8-\\xde', 45 | rsVarRange = '\\ufe0e\\ufe0f', 46 | rsBreakRange = rsMathOpRange + rsNonCharRange + rsPunctuationRange + rsSpaceRange; 47 | 48 | /** Used to compose unicode capture groups. */ 49 | var rsApos = "['\u2019]", 50 | rsAstral = '[' + rsAstralRange + ']', 51 | rsBreak = '[' + rsBreakRange + ']', 52 | rsCombo = '[' + rsComboRange + ']', 53 | rsDigits = '\\d+', 54 | rsDingbat = '[' + rsDingbatRange + ']', 55 | rsLower = '[' + rsLowerRange + ']', 56 | rsMisc = '[^' + rsAstralRange + rsBreakRange + rsDigits + rsDingbatRange + rsLowerRange + rsUpperRange + ']', 57 | rsFitz = '\\ud83c[\\udffb-\\udfff]', 58 | rsModifier = '(?:' + rsCombo + '|' + rsFitz + ')', 59 | rsNonAstral = '[^' + rsAstralRange + ']', 60 | rsRegional = '(?:\\ud83c[\\udde6-\\uddff]){2}', 61 | rsSurrPair = '[\\ud800-\\udbff][\\udc00-\\udfff]', 62 | rsUpper = '[' + rsUpperRange + ']', 63 | rsZWJ = '\\u200d'; 64 | 65 | /** Used to compose unicode regexes. */ 66 | var rsMiscLower = '(?:' + rsLower + '|' + rsMisc + ')', 67 | rsMiscUpper = '(?:' + rsUpper + '|' + rsMisc + ')', 68 | rsOptContrLower = '(?:' + rsApos + '(?:d|ll|m|re|s|t|ve))?', 69 | rsOptContrUpper = '(?:' + rsApos + '(?:D|LL|M|RE|S|T|VE))?', 70 | reOptMod = rsModifier + '?', 71 | rsOptVar = '[' + rsVarRange + ']?', 72 | rsOptJoin = '(?:' + rsZWJ + '(?:' + [rsNonAstral, rsRegional, rsSurrPair].join('|') + ')' + rsOptVar + reOptMod + ')*', 73 | rsOrdLower = '\\d*(?:(?:1st|2nd|3rd|(?![123])\\dth)\\b)', 74 | rsOrdUpper = '\\d*(?:(?:1ST|2ND|3RD|(?![123])\\dTH)\\b)', 75 | rsSeq = rsOptVar + reOptMod + rsOptJoin, 76 | rsEmoji = '(?:' + [rsDingbat, rsRegional, rsSurrPair].join('|') + ')' + rsSeq, 77 | rsSymbol = '(?:' + [rsNonAstral + rsCombo + '?', rsCombo, rsRegional, rsSurrPair, rsAstral].join('|') + ')'; 78 | 79 | /** Used to match apostrophes. */ 80 | var reApos = RegExp(rsApos, 'g'); 81 | 82 | /** 83 | * Used to match [combining diacritical marks](https://en.wikipedia.org/wiki/Combining_Diacritical_Marks) and 84 | * [combining diacritical marks for symbols](https://en.wikipedia.org/wiki/Combining_Diacritical_Marks_for_Symbols). 85 | */ 86 | var reComboMark = RegExp(rsCombo, 'g'); 87 | 88 | /** Used to match [string symbols](https://mathiasbynens.be/notes/javascript-unicode). */ 89 | var reUnicode = RegExp(rsFitz + '(?=' + rsFitz + ')|' + rsSymbol + rsSeq, 'g'); 90 | 91 | /** Used to match complex or compound words. */ 92 | var reUnicodeWord = RegExp([ 93 | rsUpper + '?' + rsLower + '+' + rsOptContrLower + '(?=' + [rsBreak, rsUpper, '$'].join('|') + ')', 94 | rsMiscUpper + '+' + rsOptContrUpper + '(?=' + [rsBreak, rsUpper + rsMiscLower, '$'].join('|') + ')', 95 | rsUpper + '?' + rsMiscLower + '+' + rsOptContrLower, 96 | rsUpper + '+' + rsOptContrUpper, 97 | rsOrdUpper, 98 | rsOrdLower, 99 | rsDigits, 100 | rsEmoji 101 | ].join('|'), 'g'); 102 | 103 | /** Used to detect strings with [zero-width joiners or code points from the astral planes](http://eev.ee/blog/2015/09/12/dark-corners-of-unicode/). */ 104 | var reHasUnicode = RegExp('[' + rsZWJ + rsAstralRange + rsComboRange + rsVarRange + ']'); 105 | 106 | /** Used to detect strings that need a more robust regexp to match words. */ 107 | var reHasUnicodeWord = /[a-z][A-Z]|[A-Z]{2,}[a-z]|[0-9][a-zA-Z]|[a-zA-Z][0-9]|[^a-zA-Z0-9 ]/; 108 | 109 | /** Used to map Latin Unicode letters to basic Latin letters. */ 110 | var deburredLetters = { 111 | // Latin-1 Supplement block. 112 | '\xc0': 'A', '\xc1': 'A', '\xc2': 'A', '\xc3': 'A', '\xc4': 'A', '\xc5': 'A', 113 | '\xe0': 'a', '\xe1': 'a', '\xe2': 'a', '\xe3': 'a', '\xe4': 'a', '\xe5': 'a', 114 | '\xc7': 'C', '\xe7': 'c', 115 | '\xd0': 'D', '\xf0': 'd', 116 | '\xc8': 'E', '\xc9': 'E', '\xca': 'E', '\xcb': 'E', 117 | '\xe8': 'e', '\xe9': 'e', '\xea': 'e', '\xeb': 'e', 118 | '\xcc': 'I', '\xcd': 'I', '\xce': 'I', '\xcf': 'I', 119 | '\xec': 'i', '\xed': 'i', '\xee': 'i', '\xef': 'i', 120 | '\xd1': 'N', '\xf1': 'n', 121 | '\xd2': 'O', '\xd3': 'O', '\xd4': 'O', '\xd5': 'O', '\xd6': 'O', '\xd8': 'O', 122 | '\xf2': 'o', '\xf3': 'o', '\xf4': 'o', '\xf5': 'o', '\xf6': 'o', '\xf8': 'o', 123 | '\xd9': 'U', '\xda': 'U', '\xdb': 'U', '\xdc': 'U', 124 | '\xf9': 'u', '\xfa': 'u', '\xfb': 'u', '\xfc': 'u', 125 | '\xdd': 'Y', '\xfd': 'y', '\xff': 'y', 126 | '\xc6': 'Ae', '\xe6': 'ae', 127 | '\xde': 'Th', '\xfe': 'th', 128 | '\xdf': 'ss', 129 | // Latin Extended-A block. 130 | '\u0100': 'A', '\u0102': 'A', '\u0104': 'A', 131 | '\u0101': 'a', '\u0103': 'a', '\u0105': 'a', 132 | '\u0106': 'C', '\u0108': 'C', '\u010a': 'C', '\u010c': 'C', 133 | '\u0107': 'c', '\u0109': 'c', '\u010b': 'c', '\u010d': 'c', 134 | '\u010e': 'D', '\u0110': 'D', '\u010f': 'd', '\u0111': 'd', 135 | '\u0112': 'E', '\u0114': 'E', '\u0116': 'E', '\u0118': 'E', '\u011a': 'E', 136 | '\u0113': 'e', '\u0115': 'e', '\u0117': 'e', '\u0119': 'e', '\u011b': 'e', 137 | '\u011c': 'G', '\u011e': 'G', '\u0120': 'G', '\u0122': 'G', 138 | '\u011d': 'g', '\u011f': 'g', '\u0121': 'g', '\u0123': 'g', 139 | '\u0124': 'H', '\u0126': 'H', '\u0125': 'h', '\u0127': 'h', 140 | '\u0128': 'I', '\u012a': 'I', '\u012c': 'I', '\u012e': 'I', '\u0130': 'I', 141 | '\u0129': 'i', '\u012b': 'i', '\u012d': 'i', '\u012f': 'i', '\u0131': 'i', 142 | '\u0134': 'J', '\u0135': 'j', 143 | '\u0136': 'K', '\u0137': 'k', '\u0138': 'k', 144 | '\u0139': 'L', '\u013b': 'L', '\u013d': 'L', '\u013f': 'L', '\u0141': 'L', 145 | '\u013a': 'l', '\u013c': 'l', '\u013e': 'l', '\u0140': 'l', '\u0142': 'l', 146 | '\u0143': 'N', '\u0145': 'N', '\u0147': 'N', '\u014a': 'N', 147 | '\u0144': 'n', '\u0146': 'n', '\u0148': 'n', '\u014b': 'n', 148 | '\u014c': 'O', '\u014e': 'O', '\u0150': 'O', 149 | '\u014d': 'o', '\u014f': 'o', '\u0151': 'o', 150 | '\u0154': 'R', '\u0156': 'R', '\u0158': 'R', 151 | '\u0155': 'r', '\u0157': 'r', '\u0159': 'r', 152 | '\u015a': 'S', '\u015c': 'S', '\u015e': 'S', '\u0160': 'S', 153 | '\u015b': 's', '\u015d': 's', '\u015f': 's', '\u0161': 's', 154 | '\u0162': 'T', '\u0164': 'T', '\u0166': 'T', 155 | '\u0163': 't', '\u0165': 't', '\u0167': 't', 156 | '\u0168': 'U', '\u016a': 'U', '\u016c': 'U', '\u016e': 'U', '\u0170': 'U', '\u0172': 'U', 157 | '\u0169': 'u', '\u016b': 'u', '\u016d': 'u', '\u016f': 'u', '\u0171': 'u', '\u0173': 'u', 158 | '\u0174': 'W', '\u0175': 'w', 159 | '\u0176': 'Y', '\u0177': 'y', '\u0178': 'Y', 160 | '\u0179': 'Z', '\u017b': 'Z', '\u017d': 'Z', 161 | '\u017a': 'z', '\u017c': 'z', '\u017e': 'z', 162 | '\u0132': 'IJ', '\u0133': 'ij', 163 | '\u0152': 'Oe', '\u0153': 'oe', 164 | '\u0149': "'n", '\u017f': 's' 165 | }; 166 | 167 | /** Detect free variable `global` from Node.js. */ 168 | var freeGlobal = typeof global == 'object' && global && global.Object === Object && global; 169 | 170 | /** Detect free variable `self`. */ 171 | var freeSelf = typeof self == 'object' && self && self.Object === Object && self; 172 | 173 | /** Used as a reference to the global object. */ 174 | var root = freeGlobal || freeSelf || Function('return this')(); 175 | 176 | /** Detect free variable `exports`. */ 177 | var freeExports = typeof exports == 'object' && exports && !exports.nodeType && exports; 178 | 179 | /** Detect free variable `module`. */ 180 | var freeModule = freeExports && typeof module == 'object' && module && !module.nodeType && module; 181 | 182 | /*--------------------------------------------------------------------------*/ 183 | 184 | /** 185 | * A specialized version of `_.map` for arrays without support for iteratee 186 | * shorthands. 187 | * 188 | * @private 189 | * @param {Array} [array] The array to iterate over. 190 | * @param {Function} iteratee The function invoked per iteration. 191 | * @returns {Array} Returns the new mapped array. 192 | */ 193 | function arrayMap(array, iteratee) { 194 | var index = -1, 195 | length = array == null ? 0 : array.length, 196 | result = Array(length); 197 | 198 | while (++index < length) { 199 | result[index] = iteratee(array[index], index, array); 200 | } 201 | return result; 202 | } 203 | 204 | /** 205 | * A specialized version of `_.reduce` for arrays without support for 206 | * iteratee shorthands. 207 | * 208 | * @private 209 | * @param {Array} [array] The array to iterate over. 210 | * @param {Function} iteratee The function invoked per iteration. 211 | * @param {*} [accumulator] The initial value. 212 | * @param {boolean} [initAccum] Specify using the first element of `array` as 213 | * the initial value. 214 | * @returns {*} Returns the accumulated value. 215 | */ 216 | function arrayReduce(array, iteratee, accumulator, initAccum) { 217 | var index = -1, 218 | length = array == null ? 0 : array.length; 219 | 220 | if (initAccum && length) { 221 | accumulator = array[++index]; 222 | } 223 | while (++index < length) { 224 | accumulator = iteratee(accumulator, array[index], index, array); 225 | } 226 | return accumulator; 227 | } 228 | 229 | /** 230 | * Converts an ASCII `string` to an array. 231 | * 232 | * @private 233 | * @param {string} string The string to convert. 234 | * @returns {Array} Returns the converted array. 235 | */ 236 | function asciiToArray(string) { 237 | return string.split(''); 238 | } 239 | 240 | /** 241 | * Splits an ASCII `string` into an array of its words. 242 | * 243 | * @private 244 | * @param {string} The string to inspect. 245 | * @returns {Array} Returns the words of `string`. 246 | */ 247 | function asciiWords(string) { 248 | return string.match(reAsciiWord) || []; 249 | } 250 | 251 | /** 252 | * The base implementation of `_.propertyOf` without support for deep paths. 253 | * 254 | * @private 255 | * @param {Object} object The object to query. 256 | * @returns {Function} Returns the new accessor function. 257 | */ 258 | function basePropertyOf(object) { 259 | return function(key) { 260 | return object == null ? undefined : object[key]; 261 | }; 262 | } 263 | 264 | /** 265 | * Used by `_.deburr` to convert Latin-1 Supplement and Latin Extended-A 266 | * letters to basic Latin letters. 267 | * 268 | * @private 269 | * @param {string} letter The matched letter to deburr. 270 | * @returns {string} Returns the deburred letter. 271 | */ 272 | var deburrLetter = basePropertyOf(deburredLetters); 273 | 274 | /** 275 | * Checks if `string` contains Unicode symbols. 276 | * 277 | * @private 278 | * @param {string} string The string to inspect. 279 | * @returns {boolean} Returns `true` if a symbol is found, else `false`. 280 | */ 281 | function hasUnicode(string) { 282 | return reHasUnicode.test(string); 283 | } 284 | 285 | /** 286 | * Checks if `string` contains a word composed of Unicode symbols. 287 | * 288 | * @private 289 | * @param {string} string The string to inspect. 290 | * @returns {boolean} Returns `true` if a word is found, else `false`. 291 | */ 292 | function hasUnicodeWord(string) { 293 | return reHasUnicodeWord.test(string); 294 | } 295 | 296 | /** 297 | * Converts `string` to an array. 298 | * 299 | * @private 300 | * @param {string} string The string to convert. 301 | * @returns {Array} Returns the converted array. 302 | */ 303 | function stringToArray(string) { 304 | return hasUnicode(string) 305 | ? unicodeToArray(string) 306 | : asciiToArray(string); 307 | } 308 | 309 | /** 310 | * Converts a Unicode `string` to an array. 311 | * 312 | * @private 313 | * @param {string} string The string to convert. 314 | * @returns {Array} Returns the converted array. 315 | */ 316 | function unicodeToArray(string) { 317 | return string.match(reUnicode) || []; 318 | } 319 | 320 | /** 321 | * Splits a Unicode `string` into an array of its words. 322 | * 323 | * @private 324 | * @param {string} The string to inspect. 325 | * @returns {Array} Returns the words of `string`. 326 | */ 327 | function unicodeWords(string) { 328 | return string.match(reUnicodeWord) || []; 329 | } 330 | 331 | /*--------------------------------------------------------------------------*/ 332 | 333 | /** Used for built-in method references. */ 334 | var objectProto = Object.prototype; 335 | 336 | /** Used to check objects for own properties. */ 337 | var hasOwnProperty = objectProto.hasOwnProperty; 338 | 339 | /** 340 | * Used to resolve the 341 | * [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring) 342 | * of values. 343 | */ 344 | var nativeObjectToString = objectProto.toString; 345 | 346 | /** Built-in value references. */ 347 | var Symbol = root.Symbol, 348 | symToStringTag = Symbol ? Symbol.toStringTag : undefined; 349 | 350 | /** Used to lookup unminified function names. */ 351 | var realNames = {}; 352 | 353 | /** Used to convert symbols to primitives and strings. */ 354 | var symbolProto = Symbol ? Symbol.prototype : undefined, 355 | symbolToString = symbolProto ? symbolProto.toString : undefined; 356 | 357 | /*------------------------------------------------------------------------*/ 358 | 359 | /** 360 | * Creates a `lodash` object which wraps `value` to enable implicit method 361 | * chain sequences. Methods that operate on and return arrays, collections, 362 | * and functions can be chained together. Methods that retrieve a single value 363 | * or may return a primitive value will automatically end the chain sequence 364 | * and return the unwrapped value. Otherwise, the value must be unwrapped 365 | * with `_#value`. 366 | * 367 | * Explicit chain sequences, which must be unwrapped with `_#value`, may be 368 | * enabled using `_.chain`. 369 | * 370 | * The execution of chained methods is lazy, that is, it's deferred until 371 | * `_#value` is implicitly or explicitly called. 372 | * 373 | * Lazy evaluation allows several methods to support shortcut fusion. 374 | * Shortcut fusion is an optimization to merge iteratee calls; this avoids 375 | * the creation of intermediate arrays and can greatly reduce the number of 376 | * iteratee executions. Sections of a chain sequence qualify for shortcut 377 | * fusion if the section is applied to an array and iteratees accept only 378 | * one argument. The heuristic for whether a section qualifies for shortcut 379 | * fusion is subject to change. 380 | * 381 | * Chaining is supported in custom builds as long as the `_#value` method is 382 | * directly or indirectly included in the build. 383 | * 384 | * In addition to lodash methods, wrappers have `Array` and `String` methods. 385 | * 386 | * The wrapper `Array` methods are: 387 | * `concat`, `join`, `pop`, `push`, `shift`, `sort`, `splice`, and `unshift` 388 | * 389 | * The wrapper `String` methods are: 390 | * `replace` and `split` 391 | * 392 | * The wrapper methods that support shortcut fusion are: 393 | * `at`, `compact`, `drop`, `dropRight`, `dropWhile`, `filter`, `find`, 394 | * `findLast`, `head`, `initial`, `last`, `map`, `reject`, `reverse`, `slice`, 395 | * `tail`, `take`, `takeRight`, `takeRightWhile`, `takeWhile`, and `toArray` 396 | * 397 | * The chainable wrapper methods are: 398 | * `after`, `ary`, `assign`, `assignIn`, `assignInWith`, `assignWith`, `at`, 399 | * `before`, `bind`, `bindAll`, `bindKey`, `castArray`, `chain`, `chunk`, 400 | * `commit`, `compact`, `concat`, `conforms`, `constant`, `countBy`, `create`, 401 | * `curry`, `debounce`, `defaults`, `defaultsDeep`, `defer`, `delay`, 402 | * `difference`, `differenceBy`, `differenceWith`, `drop`, `dropRight`, 403 | * `dropRightWhile`, `dropWhile`, `extend`, `extendWith`, `fill`, `filter`, 404 | * `flatMap`, `flatMapDeep`, `flatMapDepth`, `flatten`, `flattenDeep`, 405 | * `flattenDepth`, `flip`, `flow`, `flowRight`, `fromPairs`, `functions`, 406 | * `functionsIn`, `groupBy`, `initial`, `intersection`, `intersectionBy`, 407 | * `intersectionWith`, `invert`, `invertBy`, `invokeMap`, `iteratee`, `keyBy`, 408 | * `keys`, `keysIn`, `map`, `mapKeys`, `mapValues`, `matches`, `matchesProperty`, 409 | * `memoize`, `merge`, `mergeWith`, `method`, `methodOf`, `mixin`, `negate`, 410 | * `nthArg`, `omit`, `omitBy`, `once`, `orderBy`, `over`, `overArgs`, 411 | * `overEvery`, `overSome`, `partial`, `partialRight`, `partition`, `pick`, 412 | * `pickBy`, `plant`, `property`, `propertyOf`, `pull`, `pullAll`, `pullAllBy`, 413 | * `pullAllWith`, `pullAt`, `push`, `range`, `rangeRight`, `rearg`, `reject`, 414 | * `remove`, `rest`, `reverse`, `sampleSize`, `set`, `setWith`, `shuffle`, 415 | * `slice`, `sort`, `sortBy`, `splice`, `spread`, `tail`, `take`, `takeRight`, 416 | * `takeRightWhile`, `takeWhile`, `tap`, `throttle`, `thru`, `toArray`, 417 | * `toPairs`, `toPairsIn`, `toPath`, `toPlainObject`, `transform`, `unary`, 418 | * `union`, `unionBy`, `unionWith`, `uniq`, `uniqBy`, `uniqWith`, `unset`, 419 | * `unshift`, `unzip`, `unzipWith`, `update`, `updateWith`, `values`, 420 | * `valuesIn`, `without`, `wrap`, `xor`, `xorBy`, `xorWith`, `zip`, 421 | * `zipObject`, `zipObjectDeep`, and `zipWith` 422 | * 423 | * The wrapper methods that are **not** chainable by default are: 424 | * `add`, `attempt`, `camelCase`, `capitalize`, `ceil`, `clamp`, `clone`, 425 | * `cloneDeep`, `cloneDeepWith`, `cloneWith`, `conformsTo`, `deburr`, 426 | * `defaultTo`, `divide`, `each`, `eachRight`, `endsWith`, `eq`, `escape`, 427 | * `escapeRegExp`, `every`, `find`, `findIndex`, `findKey`, `findLast`, 428 | * `findLastIndex`, `findLastKey`, `first`, `floor`, `forEach`, `forEachRight`, 429 | * `forIn`, `forInRight`, `forOwn`, `forOwnRight`, `get`, `gt`, `gte`, `has`, 430 | * `hasIn`, `head`, `identity`, `includes`, `indexOf`, `inRange`, `invoke`, 431 | * `isArguments`, `isArray`, `isArrayBuffer`, `isArrayLike`, `isArrayLikeObject`, 432 | * `isBoolean`, `isBuffer`, `isDate`, `isElement`, `isEmpty`, `isEqual`, 433 | * `isEqualWith`, `isError`, `isFinite`, `isFunction`, `isInteger`, `isLength`, 434 | * `isMap`, `isMatch`, `isMatchWith`, `isNaN`, `isNative`, `isNil`, `isNull`, 435 | * `isNumber`, `isObject`, `isObjectLike`, `isPlainObject`, `isRegExp`, 436 | * `isSafeInteger`, `isSet`, `isString`, `isUndefined`, `isTypedArray`, 437 | * `isWeakMap`, `isWeakSet`, `join`, `kebabCase`, `last`, `lastIndexOf`, 438 | * `lowerCase`, `lowerFirst`, `lt`, `lte`, `max`, `maxBy`, `mean`, `meanBy`, 439 | * `min`, `minBy`, `multiply`, `noConflict`, `noop`, `now`, `nth`, `pad`, 440 | * `padEnd`, `padStart`, `parseInt`, `pop`, `random`, `reduce`, `reduceRight`, 441 | * `repeat`, `result`, `round`, `runInContext`, `sample`, `shift`, `size`, 442 | * `snakeCase`, `some`, `sortedIndex`, `sortedIndexBy`, `sortedLastIndex`, 443 | * `sortedLastIndexBy`, `startCase`, `startsWith`, `stubArray`, `stubFalse`, 444 | * `stubObject`, `stubString`, `stubTrue`, `subtract`, `sum`, `sumBy`, 445 | * `template`, `times`, `toFinite`, `toInteger`, `toJSON`, `toLength`, 446 | * `toLower`, `toNumber`, `toSafeInteger`, `toString`, `toUpper`, `trim`, 447 | * `trimEnd`, `trimStart`, `truncate`, `unescape`, `uniqueId`, `upperCase`, 448 | * `upperFirst`, `value`, and `words` 449 | * 450 | * @name _ 451 | * @constructor 452 | * @category Seq 453 | * @param {*} value The value to wrap in a `lodash` instance. 454 | * @returns {Object} Returns the new `lodash` wrapper instance. 455 | * @example 456 | * 457 | * function square(n) { 458 | * return n * n; 459 | * } 460 | * 461 | * var wrapped = _([1, 2, 3]); 462 | * 463 | * // Returns an unwrapped value. 464 | * wrapped.reduce(_.add); 465 | * // => 6 466 | * 467 | * // Returns a wrapped value. 468 | * var squares = wrapped.map(square); 469 | * 470 | * _.isArray(squares); 471 | * // => false 472 | * 473 | * _.isArray(squares.value()); 474 | * // => true 475 | */ 476 | function lodash() { 477 | // No operation performed. 478 | } 479 | 480 | /*------------------------------------------------------------------------*/ 481 | 482 | /** 483 | * The base implementation of `getTag` without fallbacks for buggy environments. 484 | * 485 | * @private 486 | * @param {*} value The value to query. 487 | * @returns {string} Returns the `toStringTag`. 488 | */ 489 | function baseGetTag(value) { 490 | if (value == null) { 491 | return value === undefined ? undefinedTag : nullTag; 492 | } 493 | return (symToStringTag && symToStringTag in Object(value)) 494 | ? getRawTag(value) 495 | : objectToString(value); 496 | } 497 | 498 | /** 499 | * The base implementation of `_.slice` without an iteratee call guard. 500 | * 501 | * @private 502 | * @param {Array} array The array to slice. 503 | * @param {number} [start=0] The start position. 504 | * @param {number} [end=array.length] The end position. 505 | * @returns {Array} Returns the slice of `array`. 506 | */ 507 | function baseSlice(array, start, end) { 508 | var index = -1, 509 | length = array.length; 510 | 511 | if (start < 0) { 512 | start = -start > length ? 0 : (length + start); 513 | } 514 | end = end > length ? length : end; 515 | if (end < 0) { 516 | end += length; 517 | } 518 | length = start > end ? 0 : ((end - start) >>> 0); 519 | start >>>= 0; 520 | 521 | var result = Array(length); 522 | while (++index < length) { 523 | result[index] = array[index + start]; 524 | } 525 | return result; 526 | } 527 | 528 | /** 529 | * The base implementation of `_.toString` which doesn't convert nullish 530 | * values to empty strings. 531 | * 532 | * @private 533 | * @param {*} value The value to process. 534 | * @returns {string} Returns the string. 535 | */ 536 | function baseToString(value) { 537 | // Exit early for strings to avoid a performance hit in some environments. 538 | if (typeof value == 'string') { 539 | return value; 540 | } 541 | if (isArray(value)) { 542 | // Recursively convert values (susceptible to call stack limits). 543 | return arrayMap(value, baseToString) + ''; 544 | } 545 | if (isSymbol(value)) { 546 | return symbolToString ? symbolToString.call(value) : ''; 547 | } 548 | var result = (value + ''); 549 | return (result == '0' && (1 / value) == -INFINITY) ? '-0' : result; 550 | } 551 | 552 | /** 553 | * Casts `array` to a slice if it's needed. 554 | * 555 | * @private 556 | * @param {Array} array The array to inspect. 557 | * @param {number} start The start position. 558 | * @param {number} [end=array.length] The end position. 559 | * @returns {Array} Returns the cast slice. 560 | */ 561 | function castSlice(array, start, end) { 562 | var length = array.length; 563 | end = end === undefined ? length : end; 564 | return (!start && end >= length) ? array : baseSlice(array, start, end); 565 | } 566 | 567 | /** 568 | * Creates a function like `_.lowerFirst`. 569 | * 570 | * @private 571 | * @param {string} methodName The name of the `String` case method to use. 572 | * @returns {Function} Returns the new case function. 573 | */ 574 | function createCaseFirst(methodName) { 575 | return function(string) { 576 | string = toString(string); 577 | 578 | var strSymbols = hasUnicode(string) 579 | ? stringToArray(string) 580 | : undefined; 581 | 582 | var chr = strSymbols 583 | ? strSymbols[0] 584 | : string.charAt(0); 585 | 586 | var trailing = strSymbols 587 | ? castSlice(strSymbols, 1).join('') 588 | : string.slice(1); 589 | 590 | return chr[methodName]() + trailing; 591 | }; 592 | } 593 | 594 | /** 595 | * Creates a function like `_.camelCase`. 596 | * 597 | * @private 598 | * @param {Function} callback The function to combine each word. 599 | * @returns {Function} Returns the new compounder function. 600 | */ 601 | function createCompounder(callback) { 602 | return function(string) { 603 | return arrayReduce(words(deburr(string).replace(reApos, '')), callback, ''); 604 | }; 605 | } 606 | 607 | /** 608 | * A specialized version of `baseGetTag` which ignores `Symbol.toStringTag` values. 609 | * 610 | * @private 611 | * @param {*} value The value to query. 612 | * @returns {string} Returns the raw `toStringTag`. 613 | */ 614 | function getRawTag(value) { 615 | var isOwn = hasOwnProperty.call(value, symToStringTag), 616 | tag = value[symToStringTag]; 617 | 618 | try { 619 | value[symToStringTag] = undefined; 620 | var unmasked = true; 621 | } catch (e) {} 622 | 623 | var result = nativeObjectToString.call(value); 624 | if (unmasked) { 625 | if (isOwn) { 626 | value[symToStringTag] = tag; 627 | } else { 628 | delete value[symToStringTag]; 629 | } 630 | } 631 | return result; 632 | } 633 | 634 | /** 635 | * Converts `value` to a string using `Object.prototype.toString`. 636 | * 637 | * @private 638 | * @param {*} value The value to convert. 639 | * @returns {string} Returns the converted string. 640 | */ 641 | function objectToString(value) { 642 | return nativeObjectToString.call(value); 643 | } 644 | 645 | /*------------------------------------------------------------------------*/ 646 | 647 | /** 648 | * Checks if `value` is classified as an `Array` object. 649 | * 650 | * @static 651 | * @memberOf _ 652 | * @since 0.1.0 653 | * @category Lang 654 | * @param {*} value The value to check. 655 | * @returns {boolean} Returns `true` if `value` is an array, else `false`. 656 | * @example 657 | * 658 | * _.isArray([1, 2, 3]); 659 | * // => true 660 | * 661 | * _.isArray(document.body.children); 662 | * // => false 663 | * 664 | * _.isArray('abc'); 665 | * // => false 666 | * 667 | * _.isArray(_.noop); 668 | * // => false 669 | */ 670 | var isArray = Array.isArray; 671 | 672 | /** 673 | * Checks if `value` is object-like. A value is object-like if it's not `null` 674 | * and has a `typeof` result of "object". 675 | * 676 | * @static 677 | * @memberOf _ 678 | * @since 4.0.0 679 | * @category Lang 680 | * @param {*} value The value to check. 681 | * @returns {boolean} Returns `true` if `value` is object-like, else `false`. 682 | * @example 683 | * 684 | * _.isObjectLike({}); 685 | * // => true 686 | * 687 | * _.isObjectLike([1, 2, 3]); 688 | * // => true 689 | * 690 | * _.isObjectLike(_.noop); 691 | * // => false 692 | * 693 | * _.isObjectLike(null); 694 | * // => false 695 | */ 696 | function isObjectLike(value) { 697 | return value != null && typeof value == 'object'; 698 | } 699 | 700 | /** 701 | * Checks if `value` is classified as a `Symbol` primitive or object. 702 | * 703 | * @static 704 | * @memberOf _ 705 | * @since 4.0.0 706 | * @category Lang 707 | * @param {*} value The value to check. 708 | * @returns {boolean} Returns `true` if `value` is a symbol, else `false`. 709 | * @example 710 | * 711 | * _.isSymbol(Symbol.iterator); 712 | * // => true 713 | * 714 | * _.isSymbol('abc'); 715 | * // => false 716 | */ 717 | function isSymbol(value) { 718 | return typeof value == 'symbol' || 719 | (isObjectLike(value) && baseGetTag(value) == symbolTag); 720 | } 721 | 722 | /** 723 | * Converts `value` to a string. An empty string is returned for `null` 724 | * and `undefined` values. The sign of `-0` is preserved. 725 | * 726 | * @static 727 | * @memberOf _ 728 | * @since 4.0.0 729 | * @category Lang 730 | * @param {*} value The value to convert. 731 | * @returns {string} Returns the converted string. 732 | * @example 733 | * 734 | * _.toString(null); 735 | * // => '' 736 | * 737 | * _.toString(-0); 738 | * // => '-0' 739 | * 740 | * _.toString([1, 2, 3]); 741 | * // => '1,2,3' 742 | */ 743 | function toString(value) { 744 | return value == null ? '' : baseToString(value); 745 | } 746 | 747 | /*------------------------------------------------------------------------*/ 748 | 749 | /** 750 | * Deburrs `string` by converting 751 | * [Latin-1 Supplement](https://en.wikipedia.org/wiki/Latin-1_Supplement_(Unicode_block)#Character_table) 752 | * and [Latin Extended-A](https://en.wikipedia.org/wiki/Latin_Extended-A) 753 | * letters to basic Latin letters and removing 754 | * [combining diacritical marks](https://en.wikipedia.org/wiki/Combining_Diacritical_Marks). 755 | * 756 | * @static 757 | * @memberOf _ 758 | * @since 3.0.0 759 | * @category String 760 | * @param {string} [string=''] The string to deburr. 761 | * @returns {string} Returns the deburred string. 762 | * @example 763 | * 764 | * _.deburr('déjà vu'); 765 | * // => 'deja vu' 766 | */ 767 | function deburr(string) { 768 | string = toString(string); 769 | return string && string.replace(reLatin, deburrLetter).replace(reComboMark, ''); 770 | } 771 | 772 | /** 773 | * Converts `string` to 774 | * [start case](https://en.wikipedia.org/wiki/Letter_case#Stylistic_or_specialised_usage). 775 | * 776 | * @static 777 | * @memberOf _ 778 | * @since 3.1.0 779 | * @category String 780 | * @param {string} [string=''] The string to convert. 781 | * @returns {string} Returns the start cased string. 782 | * @example 783 | * 784 | * _.startCase('--foo-bar--'); 785 | * // => 'Foo Bar' 786 | * 787 | * _.startCase('fooBar'); 788 | * // => 'Foo Bar' 789 | * 790 | * _.startCase('__FOO_BAR__'); 791 | * // => 'FOO BAR' 792 | */ 793 | var startCase = createCompounder(function(result, word, index) { 794 | return result + (index ? ' ' : '') + upperFirst(word); 795 | }); 796 | 797 | /** 798 | * Converts `string`, as a whole, to lower case just like 799 | * [String#toLowerCase](https://mdn.io/toLowerCase). 800 | * 801 | * @static 802 | * @memberOf _ 803 | * @since 4.0.0 804 | * @category String 805 | * @param {string} [string=''] The string to convert. 806 | * @returns {string} Returns the lower cased string. 807 | * @example 808 | * 809 | * _.toLower('--Foo-Bar--'); 810 | * // => '--foo-bar--' 811 | * 812 | * _.toLower('fooBar'); 813 | * // => 'foobar' 814 | * 815 | * _.toLower('__FOO_BAR__'); 816 | * // => '__foo_bar__' 817 | */ 818 | function toLower(value) { 819 | return toString(value).toLowerCase(); 820 | } 821 | 822 | /** 823 | * Converts the first character of `string` to upper case. 824 | * 825 | * @static 826 | * @memberOf _ 827 | * @since 4.0.0 828 | * @category String 829 | * @param {string} [string=''] The string to convert. 830 | * @returns {string} Returns the converted string. 831 | * @example 832 | * 833 | * _.upperFirst('fred'); 834 | * // => 'Fred' 835 | * 836 | * _.upperFirst('FRED'); 837 | * // => 'FRED' 838 | */ 839 | var upperFirst = createCaseFirst('toUpperCase'); 840 | 841 | /** 842 | * Splits `string` into an array of its words. 843 | * 844 | * @static 845 | * @memberOf _ 846 | * @since 3.0.0 847 | * @category String 848 | * @param {string} [string=''] The string to inspect. 849 | * @param {RegExp|string} [pattern] The pattern to match words. 850 | * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`. 851 | * @returns {Array} Returns the words of `string`. 852 | * @example 853 | * 854 | * _.words('fred, barney, & pebbles'); 855 | * // => ['fred', 'barney', 'pebbles'] 856 | * 857 | * _.words('fred, barney, & pebbles', /[^, ]+/g); 858 | * // => ['fred', 'barney', '&', 'pebbles'] 859 | */ 860 | function words(string, pattern, guard) { 861 | string = toString(string); 862 | pattern = guard ? undefined : pattern; 863 | 864 | if (pattern === undefined) { 865 | return hasUnicodeWord(string) ? unicodeWords(string) : asciiWords(string); 866 | } 867 | return string.match(pattern) || []; 868 | } 869 | 870 | /*------------------------------------------------------------------------*/ 871 | 872 | // Add methods that return wrapped values in chain sequences. 873 | lodash.words = words; 874 | 875 | /*------------------------------------------------------------------------*/ 876 | 877 | // Add methods that return unwrapped values in chain sequences. 878 | lodash.deburr = deburr; 879 | lodash.isArray = isArray; 880 | lodash.isObjectLike = isObjectLike; 881 | lodash.isSymbol = isSymbol; 882 | lodash.startCase = startCase; 883 | lodash.toLower = toLower; 884 | lodash.toString = toString; 885 | lodash.upperFirst = upperFirst; 886 | 887 | /*------------------------------------------------------------------------*/ 888 | 889 | /** 890 | * The semantic version number. 891 | * 892 | * @static 893 | * @memberOf _ 894 | * @type {string} 895 | */ 896 | lodash.VERSION = VERSION; 897 | 898 | /*--------------------------------------------------------------------------*/ 899 | 900 | // Some AMD build optimizers, like r.js, check for condition patterns like: 901 | if (typeof define == 'function' && typeof define.amd == 'object' && define.amd) { 902 | // Expose Lodash on the global object to prevent errors when Lodash is 903 | // loaded by a script tag in the presence of an AMD loader. 904 | // See http://requirejs.org/docs/errors.html#mismatch for more details. 905 | // Use `_.noConflict` to remove Lodash from the global object. 906 | root._ = lodash; 907 | 908 | // Define as an anonymous module so, through path mapping, it can be 909 | // referenced as the "underscore" module. 910 | define(function() { 911 | return lodash; 912 | }); 913 | } 914 | // Check for `exports` after `define` in case a build optimizer adds it. 915 | else if (freeModule) { 916 | // Export for Node.js. 917 | (freeModule.exports = lodash)._ = lodash; 918 | // Export for CommonJS support. 919 | freeExports._ = lodash; 920 | } 921 | else { 922 | // Export to the global object. 923 | root._ = lodash; 924 | } 925 | }.call(this)); 926 | -------------------------------------------------------------------------------- /__tests__/array-mixed.js: -------------------------------------------------------------------------------- 1 | const json2ts = require('../').json2ts; 2 | const assert = require('assert'); 3 | 4 | const json = `{ 5 | "ids": [1, "2"] 6 | } 7 | `; 8 | 9 | const expected = ` 10 | interface IRootObject { 11 | ids: (number | string)[]; 12 | } 13 | `; 14 | 15 | it('works with array of mixed number/strings (any)', function() { 16 | expect(json2ts(json)).toEqual(expected.slice(1)); 17 | }); 18 | -------------------------------------------------------------------------------- /__tests__/array-numbers.js: -------------------------------------------------------------------------------- 1 | const json2ts = require('../').json2ts; 2 | const assert = require('assert'); 3 | 4 | const json = `{ 5 | "ids": [1, 2, 3], 6 | "user": { 7 | "pets": ["dog", "cat"] 8 | } 9 | } 10 | `; 11 | 12 | const expected = ` 13 | interface IRootObject { 14 | ids: number[]; 15 | user: IUser; 16 | } 17 | interface IUser { 18 | pets: string[]; 19 | } 20 | `; 21 | 22 | it('works with array of numbers', function() { 23 | expect(json2ts(json)).toEqual(expected.slice(1)); 24 | }); 25 | -------------------------------------------------------------------------------- /__tests__/array-objs-camel.js: -------------------------------------------------------------------------------- 1 | const json2ts = require('../').json2ts; 2 | const assert = require('assert'); 3 | 4 | //language=JSON 5 | const json = `{ 6 | "carBrands": [ 7 | { 8 | "brandName": "shane" 9 | }, 10 | { 11 | "brandName": "sally" 12 | } 13 | ] 14 | } 15 | `; 16 | 17 | const expected = ` 18 | interface IRootObject { 19 | carBrands: ICarBrandsItem[]; 20 | } 21 | interface ICarBrandsItem { 22 | brandName: string; 23 | } 24 | `; 25 | 26 | it('works with array of mixed number/strings (any) with camel case array name', function() { 27 | expect(json2ts(json)).toEqual(expected.slice(1)); 28 | }); 29 | -------------------------------------------------------------------------------- /__tests__/array-objs.js: -------------------------------------------------------------------------------- 1 | const json2ts = require('../').json2ts; 2 | const assert = require('assert'); 3 | 4 | //language=JSON 5 | const json = `{ 6 | "users": [ 7 | { 8 | "name": "shane" 9 | }, 10 | { 11 | "name": "sally" 12 | } 13 | ] 14 | } 15 | `; 16 | 17 | const expected = ` 18 | interface IRootObject { 19 | users: IUsersItem[]; 20 | } 21 | interface IUsersItem { 22 | name: string; 23 | } 24 | `; 25 | 26 | it('works with array of mixed number/strings (any)', function() { 27 | expect(json2ts(json)).toEqual(expected.slice(1)); 28 | }); 29 | -------------------------------------------------------------------------------- /__tests__/boolean.js: -------------------------------------------------------------------------------- 1 | const json2ts = require('../').json2ts; 2 | const assert = require('assert'); 3 | 4 | const json = `{ 5 | "alive": true, 6 | "sad": false, 7 | "bools": [true, false] 8 | } 9 | `; 10 | 11 | const expected = ` 12 | interface IRootObject { 13 | alive: boolean; 14 | sad: boolean; 15 | bools: boolean[]; 16 | } 17 | `; 18 | 19 | it('bools', function() { 20 | expect(json2ts(json)).toEqual(expected.slice(1)); 21 | }); 22 | -------------------------------------------------------------------------------- /__tests__/chrome/out.d.ts: -------------------------------------------------------------------------------- 1 | interface IRootObject { 2 | version: IVersion; 3 | domains: IDomainsItem[]; 4 | } 5 | 6 | interface IVersion { 7 | major: string; 8 | minor: string; 9 | } 10 | 11 | interface IDomainsItem { 12 | domain: string; 13 | experimental?: boolean; 14 | types?: ITypesItem[]; 15 | commands: ICommandsItem[]; 16 | events?: IEventsItem[]; 17 | description?: string; 18 | dependencies?: string[]; 19 | deprecated?: boolean; 20 | } 21 | 22 | interface ICommandsItem { 23 | name: string; 24 | description?: string; 25 | returns?: IReturnsItem[]; 26 | parameters?: IParametersItem[]; 27 | experimental?: boolean; 28 | redirect?: string; 29 | handlers?: string[]; 30 | } 31 | 32 | interface IEventsItem { 33 | name: string; 34 | description?: string; 35 | parameters: IParametersItem[]; 36 | } 37 | 38 | interface IParametersItem { 39 | name: string; 40 | type?: string; 41 | description?: string; 42 | $ref?: string; 43 | optional?: boolean; 44 | } 45 | 46 | interface ITypesItem { 47 | id: string; 48 | type: string; 49 | 'enum'?: string[]; 50 | description?: string; 51 | properties?: IPropertiesItem[]; 52 | experimental?: boolean; 53 | items?: IItems; 54 | } 55 | 56 | interface IReturnsItem { 57 | name: string; 58 | type?: string; 59 | $ref?: string; 60 | experimental?: boolean; 61 | description?: string; 62 | items?: IItems; 63 | } 64 | 65 | interface IPropertiesItem { 66 | name: string; 67 | type?: string; 68 | optional: boolean; 69 | description: string; 70 | $ref?: string; 71 | } 72 | 73 | interface IItems { 74 | type: string; 75 | description: string; 76 | } 77 | 78 | -------------------------------------------------------------------------------- /__tests__/depth-01.js: -------------------------------------------------------------------------------- 1 | const json2ts = require('../').json2ts; 2 | const assert = require('assert'); 3 | 4 | const json = `{ 5 | "date": "01/02/03", 6 | "summary": { 7 | "url": "http://example.com", 8 | "path": "/where", 9 | "loc": { 10 | "lat": 10, 11 | "lng": 11, 12 | } 13 | } 14 | } 15 | `; 16 | 17 | const expected = ` 18 | interface IRootObject { 19 | date: string; 20 | summary: ISummary; 21 | } 22 | interface ISummary { 23 | url: string; 24 | path: string; 25 | loc: ILoc; 26 | } 27 | interface ILoc { 28 | lat: number; 29 | lng: number; 30 | } 31 | `; 32 | 33 | it('works 1 level of depth', function() { 34 | expect(json2ts(json)).toEqual(expected.slice(1)); 35 | }); 36 | -------------------------------------------------------------------------------- /__tests__/depth-02.js: -------------------------------------------------------------------------------- 1 | const json2ts = require('../').json2ts; 2 | const assert = require('assert'); 3 | 4 | const json = `{ 5 | "date": "01/02/03", 6 | "summary": { 7 | "url": "http://example.com", 8 | "path": "/where", 9 | "loc": { 10 | "lat": 10, 11 | "lng": 11, 12 | "address": { 13 | "house_number": "01", 14 | "street": "big barn lane", 15 | } 16 | } 17 | } 18 | } 19 | `; 20 | 21 | const expected = ` 22 | interface IRootObject { 23 | date: string; 24 | summary: ISummary; 25 | } 26 | interface ISummary { 27 | url: string; 28 | path: string; 29 | loc: ILoc; 30 | } 31 | interface ILoc { 32 | lat: number; 33 | lng: number; 34 | address: IAddress; 35 | } 36 | interface IAddress { 37 | house_number: string; 38 | street: string; 39 | } 40 | `; 41 | 42 | it('works 2 levels of depth', function() { 43 | expect(json2ts(json)).toEqual(expected.slice(1)); 44 | }); 45 | -------------------------------------------------------------------------------- /__tests__/dup-02.js: -------------------------------------------------------------------------------- 1 | const json2ts = require('../').json2ts; 2 | const assert = require('assert'); 3 | 4 | const json = `{ 5 | "user": { 6 | "name": "Shane", 7 | "age": 19 8 | }, 9 | "admin": { 10 | "user": { 11 | "name": "Shane", 12 | "age": 20 13 | } 14 | } 15 | } 16 | `; 17 | 18 | const expected = ` 19 | interface IRootObject { 20 | user: IUser; 21 | admin: IAdmin; 22 | } 23 | interface IUser { 24 | name: string; 25 | age: number; 26 | } 27 | interface IAdmin { 28 | user: IUser; 29 | } 30 | `; 31 | 32 | it('works when prop names differ, but members match', function() { 33 | expect(json2ts(json)).toEqual(expected.slice(1)); 34 | }); 35 | -------------------------------------------------------------------------------- /__tests__/dup-members-propname-mismatch.js: -------------------------------------------------------------------------------- 1 | const json2ts = require('../').json2ts; 2 | const assert = require('assert'); 3 | 4 | const json = `{ 5 | "geo": { 6 | "lat": 2, 7 | "lng": 3 8 | }, 9 | "loc": { 10 | "lat": 2, 11 | "lng": 3 12 | }, 13 | "geometry": { 14 | "bounds": { 15 | "lat": 2, 16 | "lng": 3 17 | } 18 | } 19 | } 20 | `; 21 | 22 | const expected = ` 23 | interface IRootObject { 24 | geo: IGeo; 25 | loc: ILoc; 26 | geometry: IGeometry; 27 | } 28 | interface IGeo { 29 | lat: number; 30 | lng: number; 31 | } 32 | interface ILoc { 33 | lat: number; 34 | lng: number; 35 | } 36 | interface IGeometry { 37 | bounds: IBounds; 38 | } 39 | interface IBounds { 40 | lat: number; 41 | lng: number; 42 | } 43 | `; 44 | 45 | it('works when prop names differ, but members match', function() { 46 | expect(json2ts(json)).toEqual(expected.slice(1)); 47 | }); 48 | -------------------------------------------------------------------------------- /__tests__/dup-members.js: -------------------------------------------------------------------------------- 1 | const json2ts = require('../').json2ts; 2 | const assert = require('assert'); 3 | 4 | const json = `{ 5 | "user": { 6 | "name": "Shane", 7 | "age": 19 8 | }, 9 | "admin": { 10 | "user": { 11 | "name": "Shane", 12 | "age": 20 13 | } 14 | } 15 | } 16 | `; 17 | 18 | const expected = ` 19 | interface IRootObject { 20 | user: IUser; 21 | admin: IAdmin; 22 | } 23 | interface IUser { 24 | name: string; 25 | age: number; 26 | } 27 | interface IAdmin { 28 | user: IUser; 29 | } 30 | `; 31 | 32 | it('works when prop names differ, but members match', function() { 33 | expect(json2ts(json)).toEqual(expected.slice(1)); 34 | }); 35 | -------------------------------------------------------------------------------- /__tests__/dup-merge.js: -------------------------------------------------------------------------------- 1 | const json2ts = require('../').json2ts; 2 | const assert = require('assert'); 3 | 4 | const json = ` 5 | { 6 | "items": [ 7 | { 8 | "attribute_code": "category_ids", 9 | "value": [ 10 | "3", 11 | "5" 12 | ] 13 | }, 14 | { 15 | "attribute_code": "options_container", 16 | "value": "container2" 17 | } 18 | ] 19 | } 20 | `; 21 | 22 | const expected = ` 23 | interface IRootObject { 24 | items: IItemsItem[]; 25 | } 26 | interface IItemsItem { 27 | attribute_code: string; 28 | value: string[] | string; 29 | } 30 | `; 31 | 32 | it('can collapse interfaces into union types', function() { 33 | expect(json2ts(json)).toEqual(expected.slice(1)); 34 | }); 35 | -------------------------------------------------------------------------------- /__tests__/dup.js: -------------------------------------------------------------------------------- 1 | const json2ts = require('../').json2ts; 2 | const assert = require('assert'); 3 | 4 | const json = `{ 5 | "summary": { 6 | "loc": { 7 | "lat": 10, 8 | "lng": 11 9 | } 10 | }, 11 | "loc": { 12 | "lat": 10, 13 | "lng": 11 14 | } 15 | } 16 | `; 17 | 18 | const expected = ` 19 | interface IRootObject { 20 | summary: ISummary; 21 | loc: ILoc; 22 | } 23 | interface ISummary { 24 | loc: ILoc; 25 | } 26 | interface ILoc { 27 | lat: number; 28 | lng: number; 29 | } 30 | `; 31 | 32 | it('works when prop name + members match', function() { 33 | expect(json2ts(json)).toEqual(expected.slice(1)); 34 | }); 35 | -------------------------------------------------------------------------------- /__tests__/fixtures.js: -------------------------------------------------------------------------------- 1 | const json2ts = require('../').json2ts; 2 | const fs = require('fs'); 3 | const data = (name) => fs.readFileSync(name, 'utf8'); 4 | 5 | it('matches gmaps', function () { 6 | const gmaps = data('__tests__/gmaps/in.json'); 7 | expect(json2ts(gmaps)).toMatchSnapshot(); 8 | }); 9 | 10 | it('matches magento', function () { 11 | const gmaps = data('__tests__/magento/product.json'); 12 | expect(json2ts(gmaps)).toMatchSnapshot(); 13 | }); 14 | 15 | it('matches magento categories', function () { 16 | const magentoCategories = data('__tests__/magento/categories.json'); 17 | expect(json2ts(magentoCategories)).toMatchSnapshot(); 18 | }); 19 | 20 | it('matches petition', function () { 21 | const gmaps = data('__tests__/petition/input.json'); 22 | expect(json2ts(gmaps)).toMatchSnapshot(); 23 | }); 24 | 25 | it('matches swagger', function () { 26 | const gmaps = data('__tests__/swagger/schema.json'); 27 | expect(json2ts(gmaps)).toMatchSnapshot(); 28 | }); 29 | 30 | -------------------------------------------------------------------------------- /__tests__/flow.js: -------------------------------------------------------------------------------- 1 | const json2ts = require('../').json2ts; 2 | const assert = require('assert'); 3 | 4 | const json = `{ 5 | "alive": true, 6 | "sad": false, 7 | "bools": [true, false] 8 | } 9 | `; 10 | 11 | const expected = ` 12 | // @flow 13 | export type IRootObject = { 14 | alive: boolean; 15 | sad: boolean; 16 | bools: boolean[]; 17 | }; 18 | `; 19 | 20 | it('supports flow output', function() { 21 | expect(json2ts(json, {flow: true})).toEqual(expected.slice(1)); 22 | }); 23 | -------------------------------------------------------------------------------- /__tests__/gmaps/in.json: -------------------------------------------------------------------------------- 1 | { 2 | "results" : [ 3 | { 4 | "address_components" : [ 5 | { 6 | "long_name" : "London", 7 | "short_name" : "London", 8 | "types" : [ "locality", "political" ] 9 | }, 10 | { 11 | "long_name" : "London", 12 | "short_name" : "London", 13 | "types" : [ "postal_town" ] 14 | }, 15 | { 16 | "long_name" : "Greater London", 17 | "short_name" : "Greater London", 18 | "types" : [ "administrative_area_level_2", "political" ] 19 | }, 20 | { 21 | "long_name" : "England", 22 | "short_name" : "England", 23 | "types" : [ "administrative_area_level_1", "political" ] 24 | }, 25 | { 26 | "long_name" : "United Kingdom", 27 | "short_name" : "GB", 28 | "types" : [ "country", "political" ] 29 | } 30 | ], 31 | "formatted_address" : "London, UK", 32 | "geometry" : { 33 | "bounds" : { 34 | "northeast" : { 35 | "lat" : 51.6723432, 36 | "lng" : 0.148271 37 | }, 38 | "southwest" : { 39 | "lat" : 51.38494009999999, 40 | "lng" : -0.3514683 41 | } 42 | }, 43 | "location" : { 44 | "lat" : 51.5073509, 45 | "lng" : -0.1277583 46 | }, 47 | "location_type" : "APPROXIMATE", 48 | "viewport" : { 49 | "northeast" : { 50 | "lat" : 51.6723432, 51 | "lng" : 0.1482319 52 | }, 53 | "southwest" : { 54 | "lat" : 51.38494009999999, 55 | "lng" : -0.3514683 56 | } 57 | } 58 | }, 59 | "place_id" : "ChIJdd4hrwug2EcRmSrV3Vo6llI", 60 | "types" : [ "locality", "political" ] 61 | } 62 | ], 63 | "status" : "OK" 64 | } 65 | -------------------------------------------------------------------------------- /__tests__/gmaps/out.d.ts: -------------------------------------------------------------------------------- 1 | interface IRootObject { 2 | version: IVersion; 3 | domains: IDomainsItem[]; 4 | } 5 | 6 | interface IVersion { 7 | major: string; 8 | minor: string; 9 | } 10 | 11 | interface IDomainsItem { 12 | domain: string; 13 | experimental?: boolean; 14 | types?: ITypesItem[]; 15 | commands: ICommandsItem[]; 16 | events?: IEventsItem[]; 17 | description?: string; 18 | dependencies?: string[]; 19 | deprecated?: boolean; 20 | } 21 | 22 | interface ICommandsItem { 23 | name: string; 24 | description?: string; 25 | returns?: IReturnsItem[]; 26 | parameters?: IParametersItem[]; 27 | experimental?: boolean; 28 | redirect?: string; 29 | handlers?: string[]; 30 | } 31 | 32 | interface IEventsItem { 33 | name: string; 34 | description?: string; 35 | parameters?: IParametersItem[]; 36 | experimental?: boolean; 37 | } 38 | 39 | interface IParametersItem { 40 | name: string; 41 | type?: string; 42 | description?: string; 43 | $ref?: string; 44 | optional?: boolean; 45 | experimental?: boolean; 46 | 'enum'?: string[]; 47 | deprecated?: boolean; 48 | items?: IItems; 49 | minItems?: number; 50 | } 51 | 52 | interface ITypesItem { 53 | id: string; 54 | type: string; 55 | 'enum'?: string[]; 56 | description?: string; 57 | properties?: IPropertiesItem[]; 58 | experimental?: boolean; 59 | items?: IItems; 60 | minItems?: number; 61 | maxItems?: number; 62 | } 63 | 64 | interface IReturnsItem { 65 | name: string; 66 | type?: string; 67 | $ref?: string; 68 | description?: string; 69 | experimental?: boolean; 70 | items?: IItems; 71 | optional?: boolean; 72 | } 73 | 74 | interface IPropertiesItem { 75 | name: string; 76 | type?: string; 77 | description?: string; 78 | optional?: boolean; 79 | $ref?: string; 80 | items?: IItems; 81 | experimental?: boolean; 82 | 'enum'?: string[]; 83 | minItems?: number; 84 | maxItems?: number; 85 | } 86 | 87 | interface IItems { 88 | $ref?: string; 89 | type?: string; 90 | 'enum'?: string[]; 91 | description?: string; 92 | } 93 | -------------------------------------------------------------------------------- /__tests__/invalid-interface-name.js: -------------------------------------------------------------------------------- 1 | const json2ts = require('../').json2ts; 2 | const assert = require('assert'); 3 | 4 | const json = `{ 5 | "POST /here": { 6 | "body": { 7 | "name": "string" 8 | } 9 | } 10 | } 11 | `; 12 | 13 | const expected = ` 14 | interface IRootObject { 15 | 'POST /here': { 16 | body: IBody; 17 | }; 18 | } 19 | interface IBody { 20 | name: string; 21 | } 22 | `; 23 | 24 | it('bails on interface extraction if name invalid', function() { 25 | expect(json2ts(json)).toEqual(expected.slice(1)); 26 | }); 27 | -------------------------------------------------------------------------------- /__tests__/invalid-propname.js: -------------------------------------------------------------------------------- 1 | const json2ts = require('../').json2ts; 2 | const assert = require('assert'); 3 | 4 | const json = `{ 5 | "in^valid": "here"; 6 | } 7 | `; 8 | 9 | const expected = ` 10 | interface IRootObject { 11 | 'in^valid': string; 12 | } 13 | `; 14 | 15 | it('quotes invalid property names', function() { 16 | expect(json2ts(json)).toEqual(expected.slice(1)); 17 | }); 18 | -------------------------------------------------------------------------------- /__tests__/large-01.js: -------------------------------------------------------------------------------- 1 | const json2ts = require('../').json2ts; 2 | const assert = require('assert'); 3 | 4 | const json = ` 5 | { 6 | "id": 15, 7 | "sku": "24-UG06", 8 | "name": "Affirm Water Bottle", 9 | "attribute_set_id": 11, 10 | "price": 7, 11 | "status": 1, 12 | "visibility": 4, 13 | "type_id": "simple", 14 | "created_at": "2016-07-11 10:19:09", 15 | "updated_at": "2016-07-11 10:19:09", 16 | "extension_attributes": [], 17 | "product_links": [], 18 | "options": [], 19 | "media_gallery_entries": [ 20 | { 21 | "id": 19, 22 | "media_type": "image", 23 | "label": "Image", 24 | "position": 1, 25 | "disabled": false, 26 | "types": [ 27 | "image", 28 | "small_image", 29 | "thumbnail" 30 | ], 31 | "file": "\/u\/g\/ug06-lb-0.jpg" 32 | }, 33 | { 34 | "id": 20, 35 | "media_type": "image", 36 | "label": "Image", 37 | "position": 2, 38 | "disabled": false, 39 | "types": [ 40 | "image", 41 | "small_image", 42 | "thumbnail" 43 | ], 44 | "file": "\/u\/g\/ug07-bk-0.jpg" 45 | }, 46 | { 47 | "id": 21, 48 | "media_type": "image", 49 | "label": "Image", 50 | "position": 3, 51 | "disabled": false, 52 | "types": [ 53 | "image", 54 | "small_image", 55 | "thumbnail" 56 | ], 57 | "file": "\/u\/g\/ug07-bk-0_alt1.jpg" 58 | }, 59 | { 60 | "id": 22, 61 | "media_type": "image", 62 | "label": "Image", 63 | "position": 4, 64 | "disabled": false, 65 | "types": [ 66 | "image", 67 | "small_image", 68 | "thumbnail" 69 | ], 70 | "file": "\/u\/g\/ug07-bk-0_alt1.jpg" 71 | } 72 | ], 73 | "tier_prices": [], 74 | "custom_attributes": [ 75 | { 76 | "attribute_code": "description", 77 | "value": "

You'll stay hydrated with ease with the Affirm Water Bottle by your side or in hand. Measurements on the outside help you keep track of how much you're drinking, while the screw-top lid prevents spills. A metal carabiner clip allows you to attach it to the outside of a backpack or bag for easy access.<\/p>\n

    \n
  • Made of plastic.<\/li>\n
  • Grooved sides for an easy grip.<\/li>\n<\/ul>" 78 | }, 79 | { 80 | "attribute_code": "image", 81 | "value": "\/u\/g\/ug06-lb-0.jpg" 82 | }, 83 | { 84 | "attribute_code": "small_image", 85 | "value": "\/u\/g\/ug06-lb-0.jpg" 86 | }, 87 | { 88 | "attribute_code": "thumbnail", 89 | "value": "\/u\/g\/ug06-lb-0.jpg" 90 | }, 91 | { 92 | "attribute_code": "category_ids", 93 | "value": [ 94 | "3", 95 | "5" 96 | ] 97 | }, 98 | { 99 | "attribute_code": "options_container", 100 | "value": "container2" 101 | }, 102 | { 103 | "attribute_code": "required_options", 104 | "value": "0" 105 | }, 106 | { 107 | "attribute_code": "has_options", 108 | "value": "0" 109 | }, 110 | { 111 | "attribute_code": "url_key", 112 | "value": "affirm-water-bottle" 113 | }, 114 | { 115 | "attribute_code": "tax_class_id", 116 | "value": "2" 117 | }, 118 | { 119 | "attribute_code": "activity", 120 | "value": "8,9,17,11" 121 | }, 122 | { 123 | "attribute_code": "material", 124 | "value": "44" 125 | }, 126 | { 127 | "attribute_code": "gender", 128 | "value": "80,81,82,83,84" 129 | }, 130 | { 131 | "attribute_code": "category_gear", 132 | "value": "87,89" 133 | } 134 | ] 135 | } 136 | `; 137 | 138 | const expected = ` 139 | interface IRootObject { 140 | id: number; 141 | sku: string; 142 | name: string; 143 | attribute_set_id: number; 144 | price: number; 145 | status: number; 146 | visibility: number; 147 | type_id: string; 148 | created_at: string; 149 | updated_at: string; 150 | extension_attributes: any[]; 151 | product_links: any[]; 152 | options: any[]; 153 | media_gallery_entries: IMediaGalleryEntriesItem[]; 154 | tier_prices: any[]; 155 | custom_attributes: ICustomAttributesItem[]; 156 | } 157 | interface IMediaGalleryEntriesItem { 158 | id: number; 159 | media_type: string; 160 | label: string; 161 | position: number; 162 | disabled: boolean; 163 | types: string[]; 164 | file: string; 165 | } 166 | interface ICustomAttributesItem { 167 | attribute_code: string; 168 | value: string | string[]; 169 | } 170 | `; 171 | 172 | it('works with large objects', function() { 173 | expect(json2ts(json)).toEqual(expected.slice(1)); 174 | }); 175 | -------------------------------------------------------------------------------- /__tests__/magento/categories-out.d.ts: -------------------------------------------------------------------------------- 1 | interface IChildrenDataItem { 2 | id: number; 3 | parent_id: number; 4 | name: string; 5 | image: null|string; 6 | is_active: boolean; 7 | include_in_menu: boolean; 8 | position: number; 9 | level: number; 10 | product_count: number; 11 | children_data: IChildrenDataItem[]; 12 | } 13 | -------------------------------------------------------------------------------- /__tests__/magento/out.d.ts: -------------------------------------------------------------------------------- 1 | interface IRootObject { 2 | id: number; 3 | sku: string; 4 | name: string; 5 | attribute_set_id: number; 6 | price: number; 7 | status: number; 8 | visibility: number; 9 | type_id: string; 10 | created_at: string; 11 | updated_at: string; 12 | extension_attributes: Array; 13 | product_links: Array; 14 | options: Array; 15 | media_gallery_entries: IMediaGalleryEntriesItem[]; 16 | tier_prices: Array; 17 | custom_attributes: ICustomAttributesItem[]; 18 | } 19 | 20 | interface IMediaGalleryEntriesItem { 21 | id: number; 22 | media_type: string; 23 | label: string; 24 | position: number; 25 | disabled: boolean; 26 | types: string[]; 27 | file: string; 28 | } 29 | 30 | interface ICustomAttributesItem { 31 | attribute_code: string; 32 | value: string|string[]; 33 | } 34 | 35 | -------------------------------------------------------------------------------- /__tests__/magento/product.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 15, 3 | "sku": "24-UG06", 4 | "name": "Affirm Water Bottle", 5 | "attribute_set_id": 11, 6 | "price": 7, 7 | "status": 1, 8 | "visibility": 4, 9 | "type_id": "simple", 10 | "created_at": "2016-07-11 10:19:09", 11 | "updated_at": "2016-07-11 10:19:09", 12 | "extension_attributes": [], 13 | "product_links": [], 14 | "options": [], 15 | "media_gallery_entries": [ 16 | { 17 | "id": 19, 18 | "media_type": "image", 19 | "label": "Image", 20 | "position": 1, 21 | "disabled": false, 22 | "types": [ 23 | "image", 24 | "small_image", 25 | "thumbnail" 26 | ], 27 | "file": "\/u\/g\/ug06-lb-0.jpg" 28 | }, 29 | { 30 | "id": 20, 31 | "media_type": "image", 32 | "label": "Image", 33 | "position": 2, 34 | "disabled": false, 35 | "types": [ 36 | "image", 37 | "small_image", 38 | "thumbnail" 39 | ], 40 | "file": "\/u\/g\/ug07-bk-0.jpg" 41 | }, 42 | { 43 | "id": 21, 44 | "media_type": "image", 45 | "label": "Image", 46 | "position": 3, 47 | "disabled": false, 48 | "types": [ 49 | "image", 50 | "small_image", 51 | "thumbnail" 52 | ], 53 | "file": "\/u\/g\/ug07-bk-0_alt1.jpg" 54 | }, 55 | { 56 | "id": 22, 57 | "media_type": "image", 58 | "label": "Image", 59 | "position": 4, 60 | "disabled": false, 61 | "types": [ 62 | "image", 63 | "small_image", 64 | "thumbnail" 65 | ], 66 | "file": "\/u\/g\/ug07-bk-0_alt1.jpg" 67 | } 68 | ], 69 | "tier_prices": [], 70 | "custom_attributes": [ 71 | { 72 | "attribute_code": "description", 73 | "value": "

    You'll stay hydrated with ease with the Affirm Water Bottle by your side or in hand. Measurements on the outside help you keep track of how much you're drinking, while the screw-top lid prevents spills. A metal carabiner clip allows you to attach it to the outside of a backpack or bag for easy access.<\/p>\n

      \n
    • Made of plastic.<\/li>\n
    • Grooved sides for an easy grip.<\/li>\n<\/ul>" 74 | }, 75 | { 76 | "attribute_code": "image", 77 | "value": "\/u\/g\/ug06-lb-0.jpg" 78 | }, 79 | { 80 | "attribute_code": "small_image", 81 | "value": "\/u\/g\/ug06-lb-0.jpg" 82 | }, 83 | { 84 | "attribute_code": "thumbnail", 85 | "value": "\/u\/g\/ug06-lb-0.jpg" 86 | }, 87 | { 88 | "attribute_code": "category_ids", 89 | "value": [ 90 | "3", 91 | "5" 92 | ] 93 | }, 94 | { 95 | "attribute_code": "options_container", 96 | "value": "container2" 97 | }, 98 | { 99 | "attribute_code": "required_options", 100 | "value": "0" 101 | }, 102 | { 103 | "attribute_code": "has_options", 104 | "value": "0" 105 | }, 106 | { 107 | "attribute_code": "url_key", 108 | "value": "affirm-water-bottle" 109 | }, 110 | { 111 | "attribute_code": "tax_class_id", 112 | "value": "2" 113 | }, 114 | { 115 | "attribute_code": "activity", 116 | "value": "8,9,17,11" 117 | }, 118 | { 119 | "attribute_code": "material", 120 | "value": "44" 121 | }, 122 | { 123 | "attribute_code": "gender", 124 | "value": "80,81,82,83,84" 125 | }, 126 | { 127 | "attribute_code": "category_gear", 128 | "value": "87,89" 129 | } 130 | ] 131 | } -------------------------------------------------------------------------------- /__tests__/missing-props.js: -------------------------------------------------------------------------------- 1 | const json2ts = require('../').json2ts; 2 | 3 | const json = ` 4 | { 5 | "items": [ 6 | { 7 | "pets": [1, 2] 8 | }, 9 | { 10 | "name": "shane" 11 | } 12 | ] 13 | } 14 | `; 15 | 16 | const expected = ` 17 | interface IRootObject { 18 | items: IItemsItem[]; 19 | } 20 | interface IItemsItem { 21 | pets?: number[]; 22 | name?: string; 23 | } 24 | `; 25 | 26 | it('can mark multiple missing props', function() { 27 | // console.log(json2ts(json)); 28 | expect(json2ts(json)).toEqual(expected.slice(1)); 29 | }); 30 | -------------------------------------------------------------------------------- /__tests__/multi-complex.js: -------------------------------------------------------------------------------- 1 | const {json2tsMulti} = require('../'); 2 | 3 | // language=JSON 4 | const json = ` 5 | { 6 | "status": "success", 7 | "cart": { 8 | "basket": { 9 | "type": "", 10 | "tax_label": "(Ex VAT)" 11 | } 12 | } 13 | } 14 | `; 15 | 16 | // language=JSON 17 | const json2 = ` 18 | { 19 | "status": "success", 20 | "cart": { 21 | "basket": { 22 | "type": "collect", 23 | "tax_label": "(Ex VAT)", 24 | "branch": "S01" 25 | } 26 | } 27 | } 28 | `; 29 | 30 | const expected = ` 31 | interface IRootObject { 32 | status: string; 33 | cart: ICart; 34 | } 35 | interface ICart { 36 | basket: IBasket; 37 | } 38 | interface IBasket { 39 | type: string; 40 | tax_label: string; 41 | branch?: string; 42 | } 43 | `; 44 | 45 | it('multiple inputs - when one is missing in each', function() { 46 | expect(json2tsMulti([json2, json])).toEqual(expected.slice(1)); 47 | // console.log(json2tsMulti([json2, json])); 48 | }); 49 | -------------------------------------------------------------------------------- /__tests__/multi-missing.js: -------------------------------------------------------------------------------- 1 | const {json2tsMulti} = require('../'); 2 | 3 | const json = `{ 4 | "name": "Shane" 5 | } 6 | `; 7 | const json2 = `{ 8 | "pets": "kittie" 9 | } 10 | `; 11 | 12 | const expected = ` 13 | interface IRootObject { 14 | pets?: string; 15 | name?: string; 16 | } 17 | `; 18 | 19 | it('multiple inputs - when one is missing in each', function() { 20 | expect(json2tsMulti([json2, json])).toEqual(expected.slice(1)); 21 | }); 22 | -------------------------------------------------------------------------------- /__tests__/multi-union.js: -------------------------------------------------------------------------------- 1 | const {json2tsMulti} = require('../'); 2 | 3 | const json = `{ 4 | "name": "Shane" 5 | } 6 | `; 7 | const json2 = `{ 8 | "name": null 9 | } 10 | `; 11 | 12 | const expected = ` 13 | interface IRootObject { 14 | name: string | null; 15 | } 16 | `; 17 | 18 | it('multiple inputs - union', function() { 19 | expect(json2tsMulti([json, json2])).toEqual(expected.slice(1)); 20 | }); 21 | -------------------------------------------------------------------------------- /__tests__/multi/01.json: -------------------------------------------------------------------------------- 1 | { 2 | "status": "success", 3 | "cart": { 4 | "basket": { 5 | "type": "", 6 | "tax_label": "(Ex VAT)" 7 | } 8 | } 9 | } -------------------------------------------------------------------------------- /__tests__/multi/02.json: -------------------------------------------------------------------------------- 1 | { 2 | "status": "success", 3 | "cart": { 4 | "basket": { 5 | "type": "", 6 | "tax_label": "(Ex VAT)", 7 | "branch": "S01" 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /__tests__/namespace.js: -------------------------------------------------------------------------------- 1 | const json2ts = require('../').json2ts; 2 | const assert = require('assert'); 3 | 4 | const json = ` 5 | { 6 | "items": [ 7 | { 8 | "attribute_code": "category_ids", 9 | "value": [1] 10 | }, 11 | { 12 | "attribute_code": "category_ids_2", 13 | "value": [2, 3] 14 | } 15 | ] 16 | } 17 | `; 18 | 19 | const expected = ` 20 | declare namespace Project { 21 | export interface IRootObject { 22 | items: IItemsItem[]; 23 | } 24 | export interface IItemsItem { 25 | attribute_code: string; 26 | value: number[]; 27 | } 28 | } 29 | `; 30 | 31 | it('can wrap in namespace', function() { 32 | expect(json2ts(json, {namespace: 'Project'})).toEqual(expected.slice(1)); 33 | }); 34 | -------------------------------------------------------------------------------- /__tests__/null.js: -------------------------------------------------------------------------------- 1 | const json2ts = require('../').json2ts; 2 | const assert = require('assert'); 3 | 4 | const json = `{ 5 | "alive": null, 6 | "nulls": [null, null] 7 | } 8 | `; 9 | 10 | const expected = ` 11 | interface IRootObject { 12 | alive: null; 13 | nulls: null[]; 14 | } 15 | `; 16 | 17 | it('nulls', function() { 18 | expect(json2ts(json)).toEqual(expected.slice(1)); 19 | }); 20 | -------------------------------------------------------------------------------- /__tests__/numbers.js: -------------------------------------------------------------------------------- 1 | const json2ts = require('../').json2ts; 2 | const assert = require('assert'); 3 | 4 | const json = ` 5 | { 6 | "num": 120, 7 | "neg": -120, 8 | "decimal": 1.2, 9 | "nums": [1, -1, 1.4] 10 | } 11 | `; 12 | 13 | const expected = ` 14 | interface IRootObject { 15 | num: number; 16 | neg: number; 17 | decimal: number; 18 | nums: number[]; 19 | } 20 | `; 21 | 22 | it('works with array of numbers', function() { 23 | expect(json2ts(json)).toEqual(expected.slice(1)); 24 | }); 25 | -------------------------------------------------------------------------------- /__tests__/petition/input.json: -------------------------------------------------------------------------------- 1 | { "format" : "linked-data-api", "version" : "0.2", "result" : {"_about" : "http://lda.data.parliament.uk/epetitions.json", "definition" : "http://lda.data.parliament.uk/meta/epetitions.json", "extendedMetadataVersion" : "http://lda.data.parliament.uk/epetitions.json?_metadata=all", "first" : "http://lda.data.parliament.uk/epetitions.json?_page=0", "hasPart" : "http://lda.data.parliament.uk/epetitions.json", "isPartOf" : "http://lda.data.parliament.uk/epetitions.json", "items" : [{"_about" : "http://data.parliament.uk/resources/727136", "created" : {"_value" : "2017-04-18T11:53:42.241Z", "_datatype" : "dateTime"} 2 | , "identifier" : {"_value" : "195537"} 3 | , "label" : {"_value" : "Referendum on Brexit to be held simultaneously to the election on 8th June."} 4 | , "numberOfSignatures" : 601, "status" : "closed"} 5 | , {"_about" : "http://data.parliament.uk/resources/726572", "created" : {"_value" : "2017-04-18T10:32:53.749Z", "_datatype" : "dateTime"} 6 | , "identifier" : {"_value" : "195498"} 7 | , "label" : {"_value" : "Invite international election observers to monitor the next General Election."} 8 | , "numberOfSignatures" : 1169, "status" : "closed"} 9 | , {"_about" : "http://data.parliament.uk/resources/727395", "created" : {"_value" : "2017-04-18T10:19:08.887Z", "_datatype" : "dateTime"} 10 | , "identifier" : {"_value" : "195490"} 11 | , "label" : {"_value" : "Allow 16 and 17 year olds to vote in the General Election on the 8th of June."} 12 | , "numberOfSignatures" : 444, "status" : "closed"} 13 | , {"_about" : "http://data.parliament.uk/resources/726558", "created" : {"_value" : "2017-04-16T19:10:27.188Z", "_datatype" : "dateTime"} 14 | , "identifier" : {"_value" : "195342"} 15 | , "label" : {"_value" : "The UK Government should retain our EU Renewable Energy targets as a minimum"} 16 | , "numberOfSignatures" : 1194, "status" : "closed"} 17 | , {"_about" : "http://data.parliament.uk/resources/726079", "created" : {"_value" : "2017-04-16T06:26:13.511Z", "_datatype" : "dateTime"} 18 | , "identifier" : {"_value" : "195293"} 19 | , "label" : {"_value" : "Afghans should get UK settlement, visit and student visa decision in two weeks."} 20 | , "numberOfSignatures" : 3118, "status" : "closed"} 21 | , {"_about" : "http://data.parliament.uk/resources/726425", "created" : {"_value" : "2017-04-14T19:45:15.621Z", "_datatype" : "dateTime"} 22 | , "identifier" : {"_value" : "195193"} 23 | , "label" : {"_value" : "Renew the UK work visa for the professional wrestlers Dahlia Black and TK Cooper"} 24 | , "numberOfSignatures" : 1533, "status" : "closed"} 25 | , {"_about" : "http://data.parliament.uk/resources/723962", "created" : {"_value" : "2017-04-14T09:01:17.448Z", "_datatype" : "dateTime"} 26 | , "identifier" : {"_value" : "195146"} 27 | , "label" : {"_value" : "Publicly confirm that gay men from Chechnya can seek asylum in the UK"} 28 | , "numberOfSignatures" : 20459, "status" : "closed"} 29 | , {"_about" : "http://data.parliament.uk/resources/721197", "created" : {"_value" : "2017-04-13T10:31:22.885Z", "_datatype" : "dateTime"} 30 | , "identifier" : {"_value" : "195077"} 31 | , "label" : {"_value" : "Scrap the \"rape clause\" and the \"family cap\" on social security payments."} 32 | , "numberOfSignatures" : 25307, "status" : "closed"} 33 | , {"_about" : "http://data.parliament.uk/resources/726101", "created" : {"_value" : "2017-04-12T12:47:05.547Z", "_datatype" : "dateTime"} 34 | , "identifier" : {"_value" : "194974"} 35 | , "label" : {"_value" : "Change Thames crossing decision from option C3 to option A14"} 36 | , "numberOfSignatures" : 2608, "status" : "closed"} 37 | , {"_about" : "http://data.parliament.uk/resources/727247", "created" : {"_value" : "2017-04-12T10:45:18.684Z", "_datatype" : "dateTime"} 38 | , "identifier" : {"_value" : "194951"} 39 | , "label" : {"_value" : "Begin an operation to evacuate members of LGBT+ community in Chechnya."} 40 | , "numberOfSignatures" : 521, "status" : "closed"} 41 | ], "itemsPerPage" : 10, "next" : "http://lda.data.parliament.uk/epetitions.json?_page=1", "page" : 0, "startIndex" : 1, "totalResults" : 8870, "type" : ["http://purl.org/linked-data/api/vocab#ListEndpoint", "http://purl.org/linked-data/api/vocab#Page"]} 42 | } 43 | -------------------------------------------------------------------------------- /__tests__/petition/output.d.ts: -------------------------------------------------------------------------------- 1 | interface IRootObject { 2 | format: string; 3 | version: string; 4 | result: IResult; 5 | } 6 | 7 | interface IResult { 8 | _about: string; 9 | definition: string; 10 | extendedMetadataVersion: string; 11 | first: string; 12 | hasPart: string; 13 | isPartOf: string; 14 | items: IItemsItem[]; 15 | itemsPerPage: number; 16 | next: string; 17 | page: number; 18 | startIndex: number; 19 | totalResults: number; 20 | type: string[]; 21 | } 22 | 23 | interface IItemsItem { 24 | _about: string; 25 | created: ICreated; 26 | identifier: IIdentifier; 27 | label: IIdentifier; 28 | numberOfSignatures: number; 29 | status: string; 30 | } 31 | 32 | interface ICreated { 33 | _value: string; 34 | _datatype: string; 35 | } 36 | 37 | interface IIdentifier { 38 | _value: string; 39 | } 40 | 41 | -------------------------------------------------------------------------------- /__tests__/prefix-empty.js: -------------------------------------------------------------------------------- 1 | const json2ts = require('../').json2ts; 2 | const assert = require('assert'); 3 | 4 | const json = ` 5 | { 6 | "231thing": [{"name": "darius"}, {"age": 42}] 7 | } 8 | `; 9 | 10 | const expected = ` 11 | interface RootObject { 12 | '231thing': _231ThingItem[]; 13 | } 14 | interface _231ThingItem { 15 | name?: string; 16 | age?: number; 17 | } 18 | `; 19 | 20 | it('works with prefix=blank string and gives valid interface names', function() { 21 | expect(json2ts(json, {prefix: ""})).toEqual(expected.slice(1)); 22 | }); 23 | -------------------------------------------------------------------------------- /__tests__/prefix.js: -------------------------------------------------------------------------------- 1 | const json2ts = require('../').json2ts; 2 | const assert = require('assert'); 3 | 4 | const json = ` 5 | { 6 | "id": 15, 7 | "sku": "24-UG06", 8 | "name": "Affirm Water Bottle", 9 | "attribute_set_id": 11, 10 | "price": 7, 11 | "status": 1, 12 | "visibility": 4, 13 | "type_id": "simple", 14 | "created_at": "2016-07-11 10:19:09", 15 | "updated_at": "2016-07-11 10:19:09", 16 | "extension_attributes": [], 17 | "product_links": [], 18 | "options": [], 19 | "media_gallery_entries": [ 20 | { 21 | "id": 19, 22 | "media_type": "image", 23 | "label": "Image", 24 | "position": 1, 25 | "disabled": false, 26 | "types": [ 27 | "image", 28 | "small_image", 29 | "thumbnail" 30 | ], 31 | "file": "\/u\/g\/ug06-lb-0.jpg" 32 | }, 33 | { 34 | "id": 20, 35 | "media_type": "image", 36 | "label": "Image", 37 | "position": 2, 38 | "disabled": false, 39 | "types": [ 40 | "image", 41 | "small_image", 42 | "thumbnail" 43 | ], 44 | "file": "\/u\/g\/ug07-bk-0.jpg" 45 | }, 46 | { 47 | "id": 21, 48 | "media_type": "image", 49 | "label": "Image", 50 | "position": 3, 51 | "disabled": false, 52 | "types": [ 53 | "image", 54 | "small_image", 55 | "thumbnail" 56 | ], 57 | "file": "\/u\/g\/ug07-bk-0_alt1.jpg" 58 | }, 59 | { 60 | "id": 22, 61 | "media_type": "image", 62 | "label": "Image", 63 | "position": 4, 64 | "disabled": false, 65 | "types": [ 66 | "image", 67 | "small_image", 68 | "thumbnail" 69 | ], 70 | "file": "\/u\/g\/ug07-bk-0_alt1.jpg" 71 | } 72 | ], 73 | "tier_prices": [], 74 | "custom_attributes": [ 75 | { 76 | "attribute_code": "description", 77 | "value": "

      You'll stay hydrated with ease with the Affirm Water Bottle by your side or in hand. Measurements on the outside help you keep track of how much you're drinking, while the screw-top lid prevents spills. A metal carabiner clip allows you to attach it to the outside of a backpack or bag for easy access.<\/p>\n

        \n
      • Made of plastic.<\/li>\n
      • Grooved sides for an easy grip.<\/li>\n<\/ul>" 78 | }, 79 | { 80 | "attribute_code": "image", 81 | "value": "\/u\/g\/ug06-lb-0.jpg" 82 | }, 83 | { 84 | "attribute_code": "small_image", 85 | "value": "\/u\/g\/ug06-lb-0.jpg" 86 | }, 87 | { 88 | "attribute_code": "thumbnail", 89 | "value": "\/u\/g\/ug06-lb-0.jpg" 90 | }, 91 | { 92 | "attribute_code": "category_ids", 93 | "value": [ 94 | "3", 95 | "5" 96 | ] 97 | }, 98 | { 99 | "attribute_code": "options_container", 100 | "value": "container2" 101 | }, 102 | { 103 | "attribute_code": "required_options", 104 | "value": "0" 105 | }, 106 | { 107 | "attribute_code": "has_options", 108 | "value": "0" 109 | }, 110 | { 111 | "attribute_code": "url_key", 112 | "value": "affirm-water-bottle" 113 | }, 114 | { 115 | "attribute_code": "tax_class_id", 116 | "value": "2" 117 | }, 118 | { 119 | "attribute_code": "activity", 120 | "value": "8,9,17,11" 121 | }, 122 | { 123 | "attribute_code": "material", 124 | "value": "44" 125 | }, 126 | { 127 | "attribute_code": "gender", 128 | "value": "80,81,82,83,84" 129 | }, 130 | { 131 | "attribute_code": "category_gear", 132 | "value": "87,89" 133 | } 134 | ] 135 | } 136 | `; 137 | 138 | const expected = ` 139 | interface RootObject { 140 | id: number; 141 | sku: string; 142 | name: string; 143 | attribute_set_id: number; 144 | price: number; 145 | status: number; 146 | visibility: number; 147 | type_id: string; 148 | created_at: string; 149 | updated_at: string; 150 | extension_attributes: any[]; 151 | product_links: any[]; 152 | options: any[]; 153 | media_gallery_entries: MediaGalleryEntriesItem[]; 154 | tier_prices: any[]; 155 | custom_attributes: CustomAttributesItem[]; 156 | } 157 | interface MediaGalleryEntriesItem { 158 | id: number; 159 | media_type: string; 160 | label: string; 161 | position: number; 162 | disabled: boolean; 163 | types: string[]; 164 | file: string; 165 | } 166 | interface CustomAttributesItem { 167 | attribute_code: string; 168 | value: string | string[]; 169 | } 170 | `; 171 | 172 | it('works with prefix=blank string', function() { 173 | expect(json2ts(json, {prefix: ""})).toEqual(expected.slice(1)); 174 | }); 175 | -------------------------------------------------------------------------------- /__tests__/rootName.js: -------------------------------------------------------------------------------- 1 | const json2ts = require('../').json2ts; 2 | const assert = require('assert'); 3 | 4 | const json = ` 5 | { 6 | "id": 15 7 | } 8 | `; 9 | 10 | const expected = ` 11 | interface MyRoot { 12 | id: number; 13 | } 14 | `; 15 | 16 | it('works with prefix=blank string', function() { 17 | expect(json2ts(json, {rootName: "MyRoot", prefix: ""})).toEqual(expected.slice(1)); 18 | }); 19 | -------------------------------------------------------------------------------- /__tests__/top-level-array-mixed.js: -------------------------------------------------------------------------------- 1 | const json2ts = require('../').json2ts; 2 | const assert = require('assert'); 3 | 4 | // language=JSON 5 | const json = ` 6 | [ 7 | { 8 | "name": "Shane" 9 | }, 10 | 2, 11 | "oops" 12 | ] 13 | `; 14 | 15 | const expected = ` 16 | type IRootObject = any[]; 17 | interface IRootObjectItem { 18 | name: string; 19 | } 20 | `; 21 | 22 | it('works with top level array + mixed values', function() { 23 | expect(json2ts(json)).toEqual(expected.slice(1)); 24 | }); 25 | -------------------------------------------------------------------------------- /__tests__/top-level-array-objects.js: -------------------------------------------------------------------------------- 1 | const json2ts = require('../').json2ts; 2 | const assert = require('assert'); 3 | 4 | // language=JSON 5 | const json = ` 6 | [ 7 | { 8 | "name": "Shane" 9 | }, 10 | { 11 | "name": "Kittie", 12 | "age": 28 13 | } 14 | ] 15 | `; 16 | 17 | const expected = ` 18 | type IRootObject = IRootObjectItem[]; 19 | interface IRootObjectItem { 20 | name: string; 21 | age?: number; 22 | } 23 | `; 24 | 25 | it('works with top level array + objects', function() { 26 | expect(json2ts(json)).toEqual(expected.slice(1)); 27 | }); 28 | -------------------------------------------------------------------------------- /__tests__/top-level-array.js: -------------------------------------------------------------------------------- 1 | const json2ts = require('../').json2ts; 2 | const assert = require('assert'); 3 | 4 | const json = ` 5 | [1, 2] 6 | `; 7 | 8 | const expected = ` 9 | type IRootObject = number[]; 10 | `; 11 | 12 | it('works with top level simple array', function() { 13 | expect(json2ts(json)).toEqual(expected.slice(1)); 14 | }); 15 | -------------------------------------------------------------------------------- /__tests__/top-level-multi-arrays.js: -------------------------------------------------------------------------------- 1 | const json2ts = require('../').json2ts; 2 | const json2tsMulti = require('../').json2tsMulti; 3 | const assert = require('assert'); 4 | 5 | // language=JSON 6 | const json = ` 7 | [{"name": "kittie"}] 8 | `; 9 | 10 | // language=JSON 11 | const json2 = ` 12 | [{"name": "shane", "age": 10}] 13 | `; 14 | 15 | const expected = ` 16 | type IRootObject = IRootObjectItem[]; 17 | interface IRootObjectItem { 18 | name: string; 19 | age?: number; 20 | } 21 | `; 22 | 23 | it('works with top level array when merging', function() { 24 | expect(json2tsMulti([json, json2])).toEqual(expected.slice(1)); 25 | }); 26 | -------------------------------------------------------------------------------- /__tests__/top-level-string.js: -------------------------------------------------------------------------------- 1 | const json2ts = require('../').json2ts; 2 | const assert = require('assert'); 3 | 4 | const json = `"6345634563"`; 5 | 6 | const expected = ` 7 | type IRootObject = string; 8 | `; 9 | 10 | it('works with top level string', function() { 11 | expect(json2ts(json)).toEqual(expected.slice(1, -1)); 12 | }); 13 | 14 | it('works with top level number', function() { 15 | expect(json2ts(1)).toEqual(`type IRootObject = number;`); 16 | }); 17 | 18 | it('works with top level null', function() { 19 | expect(json2ts('null')).toEqual(`type IRootObject = null;`); 20 | }); 21 | -------------------------------------------------------------------------------- /crossbow.yml: -------------------------------------------------------------------------------- 1 | tasks: 2 | build: 3 | - browserify 4 | - uglify 5 | - copy 6 | 7 | browserify: 8 | description: A special build for browser-globals 9 | tasks: 10 | - > 11 | @npm browserify dist/index.js -d | 12 | exorcist dist/json-ts.js.map 13 | > dist/json-ts.js 14 | 15 | uglify: 16 | - ifChanged: dist/json-ts.js 17 | tasks: > 18 | @npm uglifyjs 19 | --compress --mangle -- 20 | dist/json-ts.js > dist/json-ts.min.js 21 | && cp dist/json-ts.min.js docs/dist/json-ts.min.js 22 | - ifChanged: docs/lib/codemirror.js 23 | tasks: > 24 | @npm uglifyjs 25 | --compress --mangle -- 26 | docs/lib/codemirror.js > docs/dist/codemirror.js 27 | - ifChanged: docs/javascript/javascript.js 28 | tasks: > 29 | @npm uglifyjs 30 | --compress --mangle -- 31 | docs/javascript/javascript.js > docs/dist/javascript.js 32 | 33 | copy: > 34 | @sh cp dist/json-ts.min.js docs/json-ts.min.js -------------------------------------------------------------------------------- /docs/css/styles.css: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | } 4 | body, html { 5 | margin: 0; 6 | padding: 0; 7 | font: 15px/1.8 -apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif 8 | } 9 | .status { 10 | position: fixed; 11 | bottom: 0; 12 | z-index: 2; 13 | padding: 10px; 14 | width: 100%; 15 | } 16 | .status.error { 17 | color: red; 18 | background: rgba(254, 205, 205, .8); 19 | } 20 | .status.success { 21 | color: blue; 22 | background: rgba(210, 225, 244, .8); 23 | } 24 | .debug { 25 | margin: 0; 26 | } 27 | .hint { 28 | padding: .5em 1em; 29 | background: #294E80; 30 | color: white; 31 | } 32 | .hint p { 33 | margin: 0; 34 | } 35 | .hint.hint--left { 36 | background: #3667a9; 37 | } 38 | .CodeMirror { 39 | /* Set height, width, borders, and global font properties here */ 40 | /*height: 300px;*/ 41 | font-size: 12px; 42 | border-bottom: 2px solid #e9e9e9; 43 | height: calc(50vh - 37px - 47px); 44 | } 45 | .header { 46 | padding: 0; 47 | background: #294E80; 48 | color: white; 49 | text-align: center; 50 | } 51 | .logo { 52 | padding: .5em 1em; 53 | } 54 | .header a { 55 | color: white; 56 | } 57 | .header h1 { 58 | margin: 0; 59 | font-size: 12px; 60 | font-weight: normal; 61 | } 62 | .controls { 63 | font-size: 12px; 64 | background: #3667a9; 65 | padding: 1em 0; 66 | grid-template-columns: 50% 50%; 67 | display: none; 68 | } 69 | .controls p { 70 | margin: 0; 71 | } 72 | .field { 73 | line-height: 1; 74 | padding: 0 1em; 75 | } 76 | .field.check { 77 | display: flex; 78 | align-items: center; 79 | justify-content: center; 80 | } 81 | .field label { 82 | text-align: left; 83 | display: block; 84 | margin-bottom: 5px; 85 | } 86 | .field label span { 87 | display: block; 88 | padding: .2em 0; 89 | font-size: 14px; 90 | } 91 | .field input[type="text"] { 92 | width: 100%; 93 | height: 35px; 94 | padding-left: 10px; 95 | } 96 | .examples { 97 | background: orange; 98 | padding: 10px; 99 | position: fixed; 100 | bottom: 45px; 101 | z-index: 100; 102 | width: 100%; 103 | } 104 | @media (min-width: 600px) { 105 | .CodeMirror { 106 | height: calc(100vh - 160px); 107 | border-bottom: 0; 108 | } 109 | .header { 110 | display: flex; 111 | } 112 | .controls { 113 | display: flex; 114 | flex: 1; 115 | align-items: center; 116 | padding-left: 2em; 117 | } 118 | .field { 119 | display: flex; 120 | align-items: center; 121 | } 122 | .field label { 123 | margin-bottom: 0; 124 | margin-right: 10px; 125 | } 126 | .logo { 127 | display: flex; 128 | align-items: center; 129 | } 130 | .wrapper { 131 | display: flex; 132 | background-color: #fff; 133 | color: #444; 134 | } 135 | .wrapper > * { 136 | width: 50%; 137 | } 138 | .examples { 139 | /*position: fixed;*/ 140 | /*bottom: 0;*/ 141 | /*left: 0;*/ 142 | /*width: 50%;*/ 143 | /*display: block;*/ 144 | } 145 | } -------------------------------------------------------------------------------- /docs/dist/index.js: -------------------------------------------------------------------------------- 1 | /******/ (function(modules) { // webpackBootstrap 2 | /******/ // The module cache 3 | /******/ var installedModules = {}; 4 | /******/ 5 | /******/ // The require function 6 | /******/ function __webpack_require__(moduleId) { 7 | /******/ 8 | /******/ // Check if module is in cache 9 | /******/ if(installedModules[moduleId]) { 10 | /******/ return installedModules[moduleId].exports; 11 | /******/ } 12 | /******/ // Create a new module (and put it into the cache) 13 | /******/ var module = installedModules[moduleId] = { 14 | /******/ i: moduleId, 15 | /******/ l: false, 16 | /******/ exports: {} 17 | /******/ }; 18 | /******/ 19 | /******/ // Execute the module function 20 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 21 | /******/ 22 | /******/ // Flag the module as loaded 23 | /******/ module.l = true; 24 | /******/ 25 | /******/ // Return the exports of the module 26 | /******/ return module.exports; 27 | /******/ } 28 | /******/ 29 | /******/ 30 | /******/ // expose the modules object (__webpack_modules__) 31 | /******/ __webpack_require__.m = modules; 32 | /******/ 33 | /******/ // expose the module cache 34 | /******/ __webpack_require__.c = installedModules; 35 | /******/ 36 | /******/ // identity function for calling harmony imports with the correct context 37 | /******/ __webpack_require__.i = function(value) { return value; }; 38 | /******/ 39 | /******/ // define getter function for harmony exports 40 | /******/ __webpack_require__.d = function(exports, name, getter) { 41 | /******/ if(!__webpack_require__.o(exports, name)) { 42 | /******/ Object.defineProperty(exports, name, { 43 | /******/ configurable: false, 44 | /******/ enumerable: true, 45 | /******/ get: getter 46 | /******/ }); 47 | /******/ } 48 | /******/ }; 49 | /******/ 50 | /******/ // getDefaultExport function for compatibility with non-harmony modules 51 | /******/ __webpack_require__.n = function(module) { 52 | /******/ var getter = module && module.__esModule ? 53 | /******/ function getDefault() { return module['default']; } : 54 | /******/ function getModuleExports() { return module; }; 55 | /******/ __webpack_require__.d(getter, 'a', getter); 56 | /******/ return getter; 57 | /******/ }; 58 | /******/ 59 | /******/ // Object.prototype.hasOwnProperty.call 60 | /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; 61 | /******/ 62 | /******/ // __webpack_public_path__ 63 | /******/ __webpack_require__.p = ""; 64 | /******/ 65 | /******/ // Load entry module and return exports 66 | /******/ return __webpack_require__(__webpack_require__.s = 1); 67 | /******/ }) 68 | /************************************************************************/ 69 | /******/ ([ 70 | /* 0 */ 71 | /***/ (function(module, __webpack_exports__, __webpack_require__) { 72 | 73 | "use strict"; 74 | Object.defineProperty(__webpack_exports__, "__esModule", { value: true }); 75 | var index = typeof fetch=='function' ? fetch : function(url, options) { 76 | options = options || {}; 77 | return new Promise( function (resolve, reject) { 78 | var request = new XMLHttpRequest(); 79 | 80 | request.open(options.method || 'get', url); 81 | 82 | for (var i in options.headers) { 83 | request.setRequestHeader(i, options.headers[i]); 84 | } 85 | 86 | request.withCredentials = options.credentials=='include'; 87 | 88 | request.onload = function () { 89 | resolve(response()); 90 | }; 91 | 92 | request.onerror = reject; 93 | 94 | request.send(options.body); 95 | 96 | function response() { 97 | var keys = [], 98 | all = [], 99 | headers = {}, 100 | header; 101 | 102 | request.getAllResponseHeaders().replace(/^(.*?):\s*([\s\S]*?)$/gm, function (m, key, value) { 103 | keys.push(key = key.toLowerCase()); 104 | all.push([key, value]); 105 | header = headers[key]; 106 | headers[key] = header ? (header + "," + value) : value; 107 | }); 108 | 109 | return { 110 | ok: (request.status/200|0) == 1, // 200-399 111 | status: request.status, 112 | statusText: request.statusText, 113 | url: request.responseURL, 114 | clone: response, 115 | text: function () { return Promise.resolve(request.responseText); }, 116 | json: function () { return Promise.resolve(request.responseText).then(JSON.parse); }, 117 | xml: function () { return Promise.resolve(request.responseXML); }, 118 | blob: function () { return Promise.resolve(new Blob([request.response])); }, 119 | headers: { 120 | keys: function () { return keys; }, 121 | entries: function () { return all; }, 122 | get: function (n) { return headers[n.toLowerCase()]; }, 123 | has: function (n) { return n.toLowerCase() in headers; } 124 | } 125 | }; 126 | } 127 | }); 128 | }; 129 | 130 | /* harmony default export */ __webpack_exports__["default"] = (index); 131 | //# sourceMappingURL=unfetch.es.js.map 132 | 133 | 134 | /***/ }), 135 | /* 1 */ 136 | /***/ (function(module, exports, __webpack_require__) { 137 | 138 | const fetch = __webpack_require__(0).default; 139 | 140 | const iniialJson = getHash() || `{ 141 | "author": "shakyShane", 142 | "profile": { 143 | "links": [ 144 | { 145 | "name": "twitter", 146 | "url": "https://twitter.com/shaneOsbourne" 147 | }, 148 | { 149 | "name": "Medium", 150 | "url": "https://medium.com/@shakyShane" 151 | } 152 | ] 153 | }, 154 | "location": null, 155 | "email": null, 156 | "bio": null, 157 | "public_repos": 145, 158 | "public_gists": 69, 159 | "followers": 298, 160 | "following": 1, 161 | "created_at": "2012-04-14T17:34:37Z", 162 | "updated_at": "2017-04-26T12:43:35Z" 163 | }`; 164 | const $ = document.querySelector.bind(document); 165 | const statusElem = $('.status'); 166 | const statusText = $('.debug'); 167 | const flow = $('#flow'); 168 | const rootName = $('#root-name'); 169 | const prefix = $('#prefix'); 170 | const namespace = $('#namespace'); 171 | const examplesElem = $('#examples'); 172 | const examples = [ 173 | ['json/nested.json', 'Nested Objects & Arrays'], 174 | ['json/optional.json', 'Optional Fields'], 175 | ['json/recursive.json', 'Recursive data structures'], 176 | ['json/invalid-keys.json', 'Invalid property names'] 177 | ]; 178 | 179 | examples.forEach(([json, title]) => { 180 | const opt = document.createElement('option'); 181 | opt.value = json; 182 | opt.textContent = title; 183 | examplesElem.appendChild(opt); 184 | }); 185 | 186 | examplesElem.addEventListener('change', function () { 187 | if (examplesElem.value !== 'Select an example') { 188 | examplesElem.parentNode.classList.add('loading'); 189 | const resp = fetch(examplesElem.value).then(x => x.text()); 190 | resp.then(x => jsonInput.setValue(x)); 191 | } 192 | }); 193 | 194 | let defaults = { 195 | flow: false, 196 | namespace: '', 197 | prefix: 'I', 198 | rootName: 'RootObject' 199 | }; 200 | 201 | flow.addEventListener('change', function () { 202 | setOutput(jsonInput.getValue(), options({flow: flow.checked})); 203 | }); 204 | namespace.addEventListener('input', function () { 205 | setOutput(jsonInput.getValue(), options({namespace: namespace.value})); 206 | }); 207 | rootName.addEventListener('input', function () { 208 | setOutput(jsonInput.getValue(), options({rootName: rootName.value})); 209 | }); 210 | rootName.addEventListener('input', function () { 211 | setOutput(jsonInput.getValue(), options({rootName: rootName.value})); 212 | }); 213 | prefix.addEventListener('input', function () { 214 | setOutput(jsonInput.getValue(), options({prefix: prefix.value})); 215 | }); 216 | const ts = json2ts(iniialJson, defaults); 217 | 218 | var tsOutput = CodeMirror($('#ts'), { 219 | value: ts, 220 | mode: {name: "javascript", typescript: true} 221 | }); 222 | var jsonInput = CodeMirror($('#app'), { 223 | value: iniialJson, 224 | mode: {name: "javascript", json: true} 225 | }); 226 | 227 | function setOutput(json, options) { 228 | if (json && json.length > 3) { 229 | JSON.parse(json); 230 | const ts = json2ts(json, options); 231 | tsOutput.setValue(ts); 232 | status('success', 'JSON: All good!'); 233 | } 234 | } 235 | 236 | jsonInput.on('change', function (obj) { 237 | try { 238 | setOutput(jsonInput.getValue()); 239 | } catch (e) { 240 | status('error', `Error parsing JSON: ${e.message}`); 241 | console.log('Some JSON error?', e.message); 242 | } 243 | }); 244 | 245 | function getHash() { 246 | if (window.location.hash) { 247 | if (window.location.hash.slice(0, 5) === '#src=') { 248 | const maybe = window.location.hash.slice(5); 249 | if (maybe) { 250 | return decodeURIComponent(maybe); 251 | } 252 | } 253 | } 254 | return ""; 255 | } 256 | 257 | const share = $('a[href="#share"]'); 258 | share.addEventListener('click', function (e) { 259 | e.preventDefault(); 260 | window.location.hash = `src=${encodeURIComponent(jsonInput.getValue().trim())}`; 261 | }); 262 | 263 | function status(type, text) { 264 | statusElem.classList.remove('success'); 265 | statusElem.classList.remove('error'); 266 | statusElem.classList.add(type); 267 | statusText.textContent = text; 268 | } 269 | function options(incoming) { 270 | defaults = Object.assign({}, 271 | defaults, 272 | incoming 273 | ); 274 | return defaults; 275 | } 276 | 277 | 278 | /***/ }) 279 | /******/ ]); -------------------------------------------------------------------------------- /docs/dist/javascript.js: -------------------------------------------------------------------------------- 1 | !function(e){"object"==typeof exports&&"object"==typeof module?e(require("../../lib/codemirror")):"function"==typeof define&&define.amd?define(["../../lib/codemirror"],e):e(CodeMirror)}(function(e){"use strict";function t(e,t,r){return/^(?:operator|sof|keyword c|case|new|export|default|[\[{}\(,;:]|=>)$/.test(t.lastType)||"quasi"==t.lastType&&/\{\s*$/.test(e.string.slice(0,e.pos-(r||0)))}e.defineMode("javascript",function(r,n){function a(e){for(var t,r=!1,n=!1;null!=(t=e.next());){if(!r){if("/"==t&&!n)return;"["==t?n=!0:n&&"]"==t&&(n=!1)}r=!r&&"\\"==t}}function i(e,t,r){return Ve=e,Ee=r,t}function o(e,r){var n=e.next();if('"'==n||"'"==n)return r.tokenize=c(n),r.tokenize(e,r);if("."==n&&e.match(/^\d+(?:[eE][+\-]?\d+)?/))return i("number","number");if("."==n&&e.match(".."))return i("spread","meta");if(/[\[\]{}\(\),;\:\.]/.test(n))return i(n);if("="==n&&e.eat(">"))return i("=>","operator");if("0"==n&&e.eat(/x/i))return e.eatWhile(/[\da-f]/i),i("number","number");if("0"==n&&e.eat(/o/i))return e.eatWhile(/[0-7]/i),i("number","number");if("0"==n&&e.eat(/b/i))return e.eatWhile(/[01]/i),i("number","number");if(/\d/.test(n))return e.match(/^\d*(?:\.\d*)?(?:[eE][+\-]?\d+)?/),i("number","number");if("/"==n)return e.eat("*")?(r.tokenize=u,u(e,r)):e.eat("/")?(e.skipToEnd(),i("comment","comment")):t(e,r,1)?(a(e),e.match(/^\b(([gimyu])(?![gimyu]*\2))+\b/),i("regexp","string-2")):(e.eatWhile(Oe),i("operator","operator",e.current()));if("`"==n)return r.tokenize=l,l(e,r);if("#"==n)return e.skipToEnd(),i("error","error");if(Oe.test(n))return">"==n&&r.lexical&&">"==r.lexical.type||e.eatWhile(Oe),i("operator","operator",e.current());if(qe.test(n)){e.eatWhile(qe);var o=e.current(),s=Ce.propertyIsEnumerable(o)&&Ce[o];return s&&"."!=r.lastType?i(s.type,s.style,o):i("variable","variable",o)}}function c(e){return function(t,r){var n,a=!1;if(Ae&&"@"==t.peek()&&t.match(We))return r.tokenize=o,i("jsonld-keyword","meta");for(;null!=(n=t.next())&&(n!=e||a);)a=!a&&"\\"==n;return a||(r.tokenize=o),i("string","string")}}function u(e,t){for(var r,n=!1;r=e.next();){if("/"==r&&n){t.tokenize=o;break}n="*"==r}return i("comment","comment")}function l(e,t){for(var r,n=!1;null!=(r=e.next());){if(!n&&("`"==r||"$"==r&&e.eat("{"))){t.tokenize=o;break}n=!n&&"\\"==r}return i("quasi","string-2",e.current())}function s(e,t){t.fatArrowAt&&(t.fatArrowAt=null);var r=e.string.indexOf("=>",e.start);if(!(r<0)){if($e){var n=/:\s*(?:\w+(?:<[^>]*>|\[\])?|\{[^}]*\})\s*$/.exec(e.string.slice(e.start,r));n&&(r=n.index)}for(var a=0,i=!1,o=r-1;o>=0;--o){var c=e.string.charAt(o),u=Pe.indexOf(c);if(u>=0&&u<3){if(!a){++o;break}if(0==--a){"("==c&&(i=!0);break}}else if(u>=3&&u<6)++a;else if(qe.test(c))i=!0;else{if(/["'\/]/.test(c))return;if(i&&!a){++o;break}}}i&&!a&&(t.fatArrowAt=o)}}function f(e,t,r,n,a,i){this.indented=e,this.column=t,this.type=r,this.prev=a,this.info=i,null!=n&&(this.align=n)}function d(e,t){for(var r=e.localVars;r;r=r.next)if(r.name==t)return!0;for(var n=e.context;n;n=n.prev)for(var r=n.vars;r;r=r.next)if(r.name==t)return!0}function p(e,t,r,n,a){var i=e.cc;for(Ne.state=e,Ne.stream=a,Ne.marked=null,Ne.cc=i,Ne.style=t,e.lexical.hasOwnProperty("align")||(e.lexical.align=!0);;){if((i.length?i.pop():Te?j:g)(r,n)){for(;i.length&&i[i.length-1].lex;)i.pop()();return Ne.marked?Ne.marked:"variable"==r&&d(e,n)?"variable-2":t}}}function m(){for(var e=arguments.length-1;e>=0;e--)Ne.cc.push(arguments[e])}function v(){return m.apply(null,arguments),!0}function y(e){function t(t){for(var r=t;r;r=r.next)if(r.name==e)return!0;return!1}var r=Ne.state;if(Ne.marked="def",r.context){if(t(r.localVars))return;r.localVars={name:e,next:r.localVars}}else{if(t(r.globalVars))return;n.globalVars&&(r.globalVars={name:e,next:r.globalVars})}}function k(){Ne.state.context={prev:Ne.state.context,vars:Ne.state.localVars},Ne.state.localVars=Be}function b(){Ne.state.localVars=Ne.state.context.vars,Ne.state.context=Ne.state.context.prev}function x(e,t){var r=function(){var r=Ne.state,n=r.indented;if("stat"==r.lexical.type)n=r.lexical.indented;else for(var a=r.lexical;a&&")"==a.type&&a.align;a=a.prev)n=a.indented;r.lexical=new f(n,Ne.stream.column(),e,null,r.lexical,t)};return r.lex=!0,r}function h(){var e=Ne.state;e.lexical.prev&&(")"==e.lexical.type&&(e.indented=e.lexical.indented),e.lexical=e.lexical.prev)}function w(e){function t(r){return r==e?v():";"==e?m():v(t)}return t}function g(e,t){return"var"==e?v(x("vardef",t.length),Z,w(";"),h):"keyword a"==e?v(x("form"),V,g,h):"keyword b"==e?v(x("form"),g,h):"{"==e?v(x("}"),J,h):";"==e?v():"if"==e?("else"==Ne.state.lexical.info&&Ne.state.cc[Ne.state.cc.length-1]==h&&Ne.state.cc.pop()(),v(x("form"),V,g,h,ne)):"function"==e?v(le):"for"==e?v(x("form"),ae,g,h):"variable"==e?$e&&"type"==t?(Ne.marked="keyword",v(L,w("operator"),L,w(";"))):v(x("stat"),N):"switch"==e?v(x("form"),V,w("{"),x("}","switch"),J,h,h):"case"==e?v(j,w(":")):"default"==e?v(w(":")):"catch"==e?v(x("form"),k,w("("),se,w(")"),g,h,b):"class"==e?v(x("form"),de,h):"export"==e?v(x("stat"),ye,h):"import"==e?v(x("stat"),be,h):"module"==e?v(x("form"),_,w("{"),x("}"),J,h,h):"async"==e?v(g):"@"==t?v(j,g):m(x("stat"),j,w(";"),h)}function j(e){return E(e,!1)}function M(e){return E(e,!0)}function V(e){return"("!=e?m():v(x(")"),j,w(")"),h)}function E(e,t){if(Ne.state.fatArrowAt==Ne.stream.start){var r=t?O:C;if("("==e)return v(k,x(")"),F(_,")"),h,w("=>"),r,b);if("variable"==e)return m(k,_,w("=>"),r,b)}var n=t?T:A;return Se.hasOwnProperty(e)?v(n):"function"==e?v(le,n):"class"==e?v(x("form"),fe,h):"keyword c"==e||"async"==e?v(t?z:I):"("==e?v(x(")"),I,w(")"),h,n):"operator"==e||"spread"==e?v(t?M:j):"["==e?v(x("]"),je,h,n):"{"==e?G(H,"}",null,n):"quasi"==e?m($,n):"new"==e?v(W(t)):v()}function I(e){return e.match(/[;\}\)\],]/)?m():m(j)}function z(e){return e.match(/[;\}\)\],]/)?m():m(M)}function A(e,t){return","==e?v(j):T(e,t,!1)}function T(e,t,r){var n=0==r?A:T,a=0==r?j:M;return"=>"==e?v(k,r?O:C,b):"operator"==e?/\+\+|--/.test(t)?v(n):"?"==t?v(j,w(":"),a):v(a):"quasi"==e?m($,n):";"!=e?"("==e?G(M,")","call",n):"."==e?v(B,n):"["==e?v(x("]"),I,w("]"),h,n):void 0:void 0}function $(e,t){return"quasi"!=e?m():"${"!=t.slice(t.length-2)?v($):v(j,q)}function q(e){if("}"==e)return Ne.marked="string-2",Ne.state.tokenize=l,v($)}function C(e){return s(Ne.stream,Ne.state),m("{"==e?g:j)}function O(e){return s(Ne.stream,Ne.state),m("{"==e?g:M)}function W(e){return function(t){return"."==t?v(e?S:P):m(e?M:j)}}function P(e,t){if("target"==t)return Ne.marked="keyword",v(A)}function S(e,t){if("target"==t)return Ne.marked="keyword",v(T)}function N(e){return":"==e?v(h,g):m(A,w(";"),h)}function B(e){if("variable"==e)return Ne.marked="property",v()}function H(e,t){return"async"==e?(Ne.marked="property",v(H)):"variable"==e||"keyword"==Ne.style?(Ne.marked="property",v("get"==t||"set"==t?U:D)):"number"==e||"string"==e?(Ne.marked=Ae?"property":Ne.style+" property",v(D)):"jsonld-keyword"==e?v(D):"modifier"==e?v(H):"["==e?v(j,w("]"),D):"spread"==e?v(j):":"==e?m(D):void 0}function U(e){return"variable"!=e?m(D):(Ne.marked="property",v(le))}function D(e){return":"==e?v(M):"("==e?m(le):void 0}function F(e,t,r){function n(a,i){if(r?r.indexOf(a)>-1:","==a){var o=Ne.state.lexical;return"call"==o.info&&(o.pos=(o.pos||0)+1),v(function(r,n){return r==t||n==t?m():m(e)},n)}return a==t||i==t?v():v(w(t))}return function(r,a){return r==t||a==t?v():m(e,n)}}function G(e,t,r){for(var n=3;n"==e)return v(L)}function R(e,t){return"variable"==e||"keyword"==Ne.style?(Ne.marked="property",v(R)):"?"==t?v(R):":"==e?v(L):"["==e?v(j,K,w("]"),R):void 0}function X(e){return"variable"==e?v(X):":"==e?v(L):void 0}function Y(e,t){return"<"==t?v(x(">"),F(L,">"),h,Y):"|"==t||"."==e?v(L):"["==e?v(w("]"),Y):"extends"==t?v(L):void 0}function Z(){return m(_,K,te,re)}function _(e,t){return"modifier"==e?v(_):"variable"==e?(y(t),v()):"spread"==e?v(_):"["==e?G(_,"]"):"{"==e?G(ee,"}"):void 0}function ee(e,t){return"variable"!=e||Ne.stream.match(/^\s*:/,!1)?("variable"==e&&(Ne.marked="property"),"spread"==e?v(_):"}"==e?m():v(w(":"),_,te)):(y(t),v(te))}function te(e,t){if("="==t)return v(M)}function re(e){if(","==e)return v(Z)}function ne(e,t){if("keyword b"==e&&"else"==t)return v(x("form","else"),g,h)}function ae(e){if("("==e)return v(x(")"),ie,w(")"),h)}function ie(e){return"var"==e?v(Z,w(";"),ce):";"==e?v(ce):"variable"==e?v(oe):m(j,w(";"),ce)}function oe(e,t){return"in"==t||"of"==t?(Ne.marked="keyword",v(j)):v(A,ce)}function ce(e,t){return";"==e?v(ue):"in"==t||"of"==t?(Ne.marked="keyword",v(j)):m(j,w(";"),ue)}function ue(e){")"!=e&&v(j)}function le(e,t){return"*"==t?(Ne.marked="keyword",v(le)):"variable"==e?(y(t),v(le)):"("==e?v(k,x(")"),F(se,")"),h,K,g,b):$e&&"<"==t?v(x(">"),F(L,">"),h,le):void 0}function se(e){return"spread"==e?v(se):m(_,K,te)}function fe(e,t){return"variable"==e?de(e,t):pe(e,t)}function de(e,t){if("variable"==e)return y(t),v(pe)}function pe(e,t){return"<"==t?v(x(">"),F(L,">"),h,pe):"extends"==t||"implements"==t||$e&&","==e?v($e?L:j,pe):"{"==e?v(x("}"),me,h):void 0}function me(e,t){return"variable"==e||"keyword"==Ne.style?("async"==t||"static"==t||"get"==t||"set"==t||$e&&("public"==t||"private"==t||"protected"==t||"readonly"==t||"abstract"==t))&&Ne.stream.match(/^\s+[\w$\xa1-\uffff]/,!1)?(Ne.marked="keyword",v(me)):(Ne.marked="property",v($e?ve:le,me)):"["==e?v(j,w("]"),$e?ve:le,me):"*"==t?(Ne.marked="keyword",v(me)):";"==e?v(me):"}"==e?v():"@"==t?v(j,me):void 0}function ve(e,t){return"?"==t?v(ve):":"==e?v(L,te):"="==t?v(M):m(le)}function ye(e,t){return"*"==t?(Ne.marked="keyword",v(ge,w(";"))):"default"==t?(Ne.marked="keyword",v(j,w(";"))):"{"==e?v(F(ke,"}"),ge,w(";")):m(g)}function ke(e,t){return"as"==t?(Ne.marked="keyword",v(w("variable"))):"variable"==e?m(M,ke):void 0}function be(e){return"string"==e?v():m(xe,he,ge)}function xe(e,t){return"{"==e?G(xe,"}"):("variable"==e&&y(t),"*"==t&&(Ne.marked="keyword"),v(we))}function he(e){if(","==e)return v(xe,he)}function we(e,t){if("as"==t)return Ne.marked="keyword",v(xe)}function ge(e,t){if("from"==t)return Ne.marked="keyword",v(j)}function je(e){return"]"==e?v():m(F(M,"]"))}function Me(e,t){return"operator"==e.lastType||","==e.lastType||Oe.test(t.charAt(0))||/[,.]/.test(t.charAt(0))}var Ve,Ee,Ie=r.indentUnit,ze=n.statementIndent,Ae=n.jsonld,Te=n.json||Ae,$e=n.typescript,qe=n.wordCharacters||/[\w$\xa1-\uffff]/,Ce=function(){function e(e){return{type:e,style:"keyword"}}var t=e("keyword a"),r=e("keyword b"),n=e("keyword c"),a=e("operator"),i={type:"atom",style:"atom"},o={if:e("if"),while:t,with:t,else:r,do:r,try:r,finally:r,return:n,break:n,continue:n,new:e("new"),delete:n,throw:n,debugger:n,var:e("var"),const:e("var"),let:e("var"),function:e("function"),catch:e("catch"),for:e("for"),switch:e("switch"),case:e("case"),default:e("default"),in:a,typeof:a,instanceof:a,true:i,false:i,null:i,undefined:i,NaN:i,Infinity:i,this:e("this"),class:e("class"),super:e("atom"),yield:n,export:e("export"),import:e("import"),extends:n,await:n,async:e("async")};if($e){var c={type:"variable",style:"variable-3"},u={interface:e("class"),implements:n,namespace:n,module:e("module"),enum:e("module"),public:e("modifier"),private:e("modifier"),protected:e("modifier"),abstract:e("modifier"),as:a,string:c,number:c,boolean:c,any:c};for(var l in u)o[l]=u[l]}return o}(),Oe=/[+\-*&%=<>!?|~^@]/,We=/^@(context|id|value|language|type|container|list|set|reverse|index|base|vocab|graph)"/,Pe="([{}])",Se={atom:!0,number:!0,variable:!0,string:!0,regexp:!0,this:!0,"jsonld-keyword":!0},Ne={state:null,column:null,marked:null,cc:null},Be={name:"this",next:{name:"arguments"}};return h.lex=!0,{startState:function(e){var t={tokenize:o,lastType:"sof",cc:[],lexical:new f((e||0)-Ie,0,"block",!1),localVars:n.localVars,context:n.localVars&&{vars:n.localVars},indented:e||0};return n.globalVars&&"object"==typeof n.globalVars&&(t.globalVars=n.globalVars),t},token:function(e,t){if(e.sol()&&(t.lexical.hasOwnProperty("align")||(t.lexical.align=!1),t.indented=e.indentation(),s(e,t)),t.tokenize!=u&&e.eatSpace())return null;var r=t.tokenize(e,t);return"comment"==Ve?r:(t.lastType="operator"!=Ve||"++"!=Ee&&"--"!=Ee?Ve:"incdec",p(t,r,Ve,Ee,e))},indent:function(t,r){if(t.tokenize==u)return e.Pass;if(t.tokenize!=o)return 0;var a,i=r&&r.charAt(0),c=t.lexical;if(!/^\s*else\b/.test(r))for(var l=t.cc.length-1;l>=0;--l){var s=t.cc[l];if(s==h)c=c.prev;else if(s!=ne)break}for(;("stat"==c.type||"form"==c.type)&&("}"==i||(a=t.cc[t.cc.length-1])&&(a==A||a==T)&&!/^[,\.=+\-*:?[\(]/.test(r));)c=c.prev;ze&&")"==c.type&&"stat"==c.prev.type&&(c=c.prev);var f=c.type,d=i==f;return"vardef"==f?c.indented+("operator"==t.lastType||","==t.lastType?c.info+1:0):"form"==f&&"{"==i?c.indented:"form"==f?c.indented+Ie:"stat"==f?c.indented+(Me(t,r)?ze||Ie:0):"switch"!=c.info||d||0==n.doubleIndentSwitch?c.align?c.column+(d?0:1):c.indented+(d?0:Ie):c.indented+(/^(?:case|default)\b/.test(r)?Ie:2*Ie)},electricInput:/^\s*(?:case .*?:|default:|\{|\})$/,blockCommentStart:Te?null:"/*",blockCommentEnd:Te?null:"*/",lineComment:Te?null:"//",fold:"brace",closeBrackets:"()[]{}''\"\"``",helperType:Te?"json":"javascript",jsonldMode:Ae,jsonMode:Te,expressionAllowed:t,skipExpression:function(e){var t=e.cc[e.cc.length-1];t!=j&&t!=M||e.cc.pop()}}}),e.registerHelper("wordChars","javascript",/[\w$]/),e.defineMIME("text/javascript","javascript"),e.defineMIME("text/ecmascript","javascript"),e.defineMIME("application/javascript","javascript"),e.defineMIME("application/x-javascript","javascript"),e.defineMIME("application/ecmascript","javascript"),e.defineMIME("application/json",{name:"javascript",json:!0}),e.defineMIME("application/x-json",{name:"javascript",json:!0}),e.defineMIME("application/ld+json",{name:"javascript",jsonld:!0}),e.defineMIME("text/typescript",{name:"javascript",typescript:!0}),e.defineMIME("application/typescript",{name:"javascript",typescript:!0})}); 2 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | Convert JSON to Typescript or Flow definitions 9 | 10 | 11 | 12 | 13 |
        14 | 17 |
        18 |
        19 | Share 20 |
        21 |
        22 | 26 |
        27 |
        28 | 31 | 32 |
        33 |
        34 | 37 | 38 |
        39 |
        40 | 43 | 44 |
        45 |
        46 |
        47 |
        48 |
        49 |
        50 |
        51 |
        52 |
        53 | 54 |
        55 |
        56 |
        57 |
        58 | 59 | 62 |
        63 |

        Try me out!

        ` 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /docs/javascript/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | CodeMirror: JavaScript mode 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 27 | 28 |
        29 |

        JavaScript mode

        30 | 31 | 32 |
        81 | 82 | 90 | 91 |

        92 | JavaScript mode supports several configuration options: 93 |

          94 |
        • json which will set the mode to expect JSON 95 | data rather than a JavaScript program.
        • 96 |
        • jsonld which will set the mode to expect 97 | JSON-LD linked data rather 98 | than a JavaScript program (demo).
        • 99 |
        • typescript which will activate additional 100 | syntax highlighting and some other things for TypeScript code 101 | (demo).
        • 102 |
        • statementIndent which (given a number) will 103 | determine the amount of indentation to use for statements 104 | continued on a new line.
        • 105 |
        • wordCharacters, a regexp that indicates which 106 | characters should be considered part of an identifier. 107 | Defaults to /[\w$]/, which does not handle 108 | non-ASCII identifiers. Can be set to something more elaborate 109 | to improve Unicode support.
        • 110 |
        111 |

        112 | 113 |

        MIME types defined: text/javascript, application/json, application/ld+json, text/typescript, application/typescript.

        114 |
        115 | -------------------------------------------------------------------------------- /docs/javascript/javascript.js: -------------------------------------------------------------------------------- 1 | // CodeMirror, copyright (c) by Marijn Haverbeke and others 2 | // Distributed under an MIT license: http://codemirror.net/LICENSE 3 | 4 | (function(mod) { 5 | if (typeof exports == "object" && typeof module == "object") // CommonJS 6 | mod(require("../../lib/codemirror")); 7 | else if (typeof define == "function" && define.amd) // AMD 8 | define(["../../lib/codemirror"], mod); 9 | else // Plain browser env 10 | mod(CodeMirror); 11 | })(function(CodeMirror) { 12 | "use strict"; 13 | 14 | function expressionAllowed(stream, state, backUp) { 15 | return /^(?:operator|sof|keyword c|case|new|export|default|[\[{}\(,;:]|=>)$/.test(state.lastType) || 16 | (state.lastType == "quasi" && /\{\s*$/.test(stream.string.slice(0, stream.pos - (backUp || 0)))) 17 | } 18 | 19 | CodeMirror.defineMode("javascript", function(config, parserConfig) { 20 | var indentUnit = config.indentUnit; 21 | var statementIndent = parserConfig.statementIndent; 22 | var jsonldMode = parserConfig.jsonld; 23 | var jsonMode = parserConfig.json || jsonldMode; 24 | var isTS = parserConfig.typescript; 25 | var wordRE = parserConfig.wordCharacters || /[\w$\xa1-\uffff]/; 26 | 27 | // Tokenizer 28 | 29 | var keywords = function(){ 30 | function kw(type) {return {type: type, style: "keyword"};} 31 | var A = kw("keyword a"), B = kw("keyword b"), C = kw("keyword c"); 32 | var operator = kw("operator"), atom = {type: "atom", style: "atom"}; 33 | 34 | var jsKeywords = { 35 | "if": kw("if"), "while": A, "with": A, "else": B, "do": B, "try": B, "finally": B, 36 | "return": C, "break": C, "continue": C, "new": kw("new"), "delete": C, "throw": C, "debugger": C, 37 | "var": kw("var"), "const": kw("var"), "let": kw("var"), 38 | "function": kw("function"), "catch": kw("catch"), 39 | "for": kw("for"), "switch": kw("switch"), "case": kw("case"), "default": kw("default"), 40 | "in": operator, "typeof": operator, "instanceof": operator, 41 | "true": atom, "false": atom, "null": atom, "undefined": atom, "NaN": atom, "Infinity": atom, 42 | "this": kw("this"), "class": kw("class"), "super": kw("atom"), 43 | "yield": C, "export": kw("export"), "import": kw("import"), "extends": C, 44 | "await": C, "async": kw("async") 45 | }; 46 | 47 | // Extend the 'normal' keywords with the TypeScript language extensions 48 | if (isTS) { 49 | var type = {type: "variable", style: "variable-3"}; 50 | var tsKeywords = { 51 | // object-like things 52 | "interface": kw("class"), 53 | "implements": C, 54 | "namespace": C, 55 | "module": kw("module"), 56 | "enum": kw("module"), 57 | 58 | // scope modifiers 59 | "public": kw("modifier"), 60 | "private": kw("modifier"), 61 | "protected": kw("modifier"), 62 | "abstract": kw("modifier"), 63 | 64 | // operators 65 | "as": operator, 66 | 67 | // types 68 | "string": type, "number": type, "boolean": type, "any": type 69 | }; 70 | 71 | for (var attr in tsKeywords) { 72 | jsKeywords[attr] = tsKeywords[attr]; 73 | } 74 | } 75 | 76 | return jsKeywords; 77 | }(); 78 | 79 | var isOperatorChar = /[+\-*&%=<>!?|~^@]/; 80 | var isJsonldKeyword = /^@(context|id|value|language|type|container|list|set|reverse|index|base|vocab|graph)"/; 81 | 82 | function readRegexp(stream) { 83 | var escaped = false, next, inSet = false; 84 | while ((next = stream.next()) != null) { 85 | if (!escaped) { 86 | if (next == "/" && !inSet) return; 87 | if (next == "[") inSet = true; 88 | else if (inSet && next == "]") inSet = false; 89 | } 90 | escaped = !escaped && next == "\\"; 91 | } 92 | } 93 | 94 | // Used as scratch variables to communicate multiple values without 95 | // consing up tons of objects. 96 | var type, content; 97 | function ret(tp, style, cont) { 98 | type = tp; content = cont; 99 | return style; 100 | } 101 | function tokenBase(stream, state) { 102 | var ch = stream.next(); 103 | if (ch == '"' || ch == "'") { 104 | state.tokenize = tokenString(ch); 105 | return state.tokenize(stream, state); 106 | } else if (ch == "." && stream.match(/^\d+(?:[eE][+\-]?\d+)?/)) { 107 | return ret("number", "number"); 108 | } else if (ch == "." && stream.match("..")) { 109 | return ret("spread", "meta"); 110 | } else if (/[\[\]{}\(\),;\:\.]/.test(ch)) { 111 | return ret(ch); 112 | } else if (ch == "=" && stream.eat(">")) { 113 | return ret("=>", "operator"); 114 | } else if (ch == "0" && stream.eat(/x/i)) { 115 | stream.eatWhile(/[\da-f]/i); 116 | return ret("number", "number"); 117 | } else if (ch == "0" && stream.eat(/o/i)) { 118 | stream.eatWhile(/[0-7]/i); 119 | return ret("number", "number"); 120 | } else if (ch == "0" && stream.eat(/b/i)) { 121 | stream.eatWhile(/[01]/i); 122 | return ret("number", "number"); 123 | } else if (/\d/.test(ch)) { 124 | stream.match(/^\d*(?:\.\d*)?(?:[eE][+\-]?\d+)?/); 125 | return ret("number", "number"); 126 | } else if (ch == "/") { 127 | if (stream.eat("*")) { 128 | state.tokenize = tokenComment; 129 | return tokenComment(stream, state); 130 | } else if (stream.eat("/")) { 131 | stream.skipToEnd(); 132 | return ret("comment", "comment"); 133 | } else if (expressionAllowed(stream, state, 1)) { 134 | readRegexp(stream); 135 | stream.match(/^\b(([gimyu])(?![gimyu]*\2))+\b/); 136 | return ret("regexp", "string-2"); 137 | } else { 138 | stream.eatWhile(isOperatorChar); 139 | return ret("operator", "operator", stream.current()); 140 | } 141 | } else if (ch == "`") { 142 | state.tokenize = tokenQuasi; 143 | return tokenQuasi(stream, state); 144 | } else if (ch == "#") { 145 | stream.skipToEnd(); 146 | return ret("error", "error"); 147 | } else if (isOperatorChar.test(ch)) { 148 | if (ch != ">" || !state.lexical || state.lexical.type != ">") 149 | stream.eatWhile(isOperatorChar); 150 | return ret("operator", "operator", stream.current()); 151 | } else if (wordRE.test(ch)) { 152 | stream.eatWhile(wordRE); 153 | var word = stream.current(), known = keywords.propertyIsEnumerable(word) && keywords[word]; 154 | return (known && state.lastType != ".") ? ret(known.type, known.style, word) : 155 | ret("variable", "variable", word); 156 | } 157 | } 158 | 159 | function tokenString(quote) { 160 | return function(stream, state) { 161 | var escaped = false, next; 162 | if (jsonldMode && stream.peek() == "@" && stream.match(isJsonldKeyword)){ 163 | state.tokenize = tokenBase; 164 | return ret("jsonld-keyword", "meta"); 165 | } 166 | while ((next = stream.next()) != null) { 167 | if (next == quote && !escaped) break; 168 | escaped = !escaped && next == "\\"; 169 | } 170 | if (!escaped) state.tokenize = tokenBase; 171 | return ret("string", "string"); 172 | }; 173 | } 174 | 175 | function tokenComment(stream, state) { 176 | var maybeEnd = false, ch; 177 | while (ch = stream.next()) { 178 | if (ch == "/" && maybeEnd) { 179 | state.tokenize = tokenBase; 180 | break; 181 | } 182 | maybeEnd = (ch == "*"); 183 | } 184 | return ret("comment", "comment"); 185 | } 186 | 187 | function tokenQuasi(stream, state) { 188 | var escaped = false, next; 189 | while ((next = stream.next()) != null) { 190 | if (!escaped && (next == "`" || next == "$" && stream.eat("{"))) { 191 | state.tokenize = tokenBase; 192 | break; 193 | } 194 | escaped = !escaped && next == "\\"; 195 | } 196 | return ret("quasi", "string-2", stream.current()); 197 | } 198 | 199 | var brackets = "([{}])"; 200 | // This is a crude lookahead trick to try and notice that we're 201 | // parsing the argument patterns for a fat-arrow function before we 202 | // actually hit the arrow token. It only works if the arrow is on 203 | // the same line as the arguments and there's no strange noise 204 | // (comments) in between. Fallback is to only notice when we hit the 205 | // arrow, and not declare the arguments as locals for the arrow 206 | // body. 207 | function findFatArrow(stream, state) { 208 | if (state.fatArrowAt) state.fatArrowAt = null; 209 | var arrow = stream.string.indexOf("=>", stream.start); 210 | if (arrow < 0) return; 211 | 212 | if (isTS) { // Try to skip TypeScript return type declarations after the arguments 213 | var m = /:\s*(?:\w+(?:<[^>]*>|\[\])?|\{[^}]*\})\s*$/.exec(stream.string.slice(stream.start, arrow)) 214 | if (m) arrow = m.index 215 | } 216 | 217 | var depth = 0, sawSomething = false; 218 | for (var pos = arrow - 1; pos >= 0; --pos) { 219 | var ch = stream.string.charAt(pos); 220 | var bracket = brackets.indexOf(ch); 221 | if (bracket >= 0 && bracket < 3) { 222 | if (!depth) { ++pos; break; } 223 | if (--depth == 0) { if (ch == "(") sawSomething = true; break; } 224 | } else if (bracket >= 3 && bracket < 6) { 225 | ++depth; 226 | } else if (wordRE.test(ch)) { 227 | sawSomething = true; 228 | } else if (/["'\/]/.test(ch)) { 229 | return; 230 | } else if (sawSomething && !depth) { 231 | ++pos; 232 | break; 233 | } 234 | } 235 | if (sawSomething && !depth) state.fatArrowAt = pos; 236 | } 237 | 238 | // Parser 239 | 240 | var atomicTypes = {"atom": true, "number": true, "variable": true, "string": true, "regexp": true, "this": true, "jsonld-keyword": true}; 241 | 242 | function JSLexical(indented, column, type, align, prev, info) { 243 | this.indented = indented; 244 | this.column = column; 245 | this.type = type; 246 | this.prev = prev; 247 | this.info = info; 248 | if (align != null) this.align = align; 249 | } 250 | 251 | function inScope(state, varname) { 252 | for (var v = state.localVars; v; v = v.next) 253 | if (v.name == varname) return true; 254 | for (var cx = state.context; cx; cx = cx.prev) { 255 | for (var v = cx.vars; v; v = v.next) 256 | if (v.name == varname) return true; 257 | } 258 | } 259 | 260 | function parseJS(state, style, type, content, stream) { 261 | var cc = state.cc; 262 | // Communicate our context to the combinators. 263 | // (Less wasteful than consing up a hundred closures on every call.) 264 | cx.state = state; cx.stream = stream; cx.marked = null, cx.cc = cc; cx.style = style; 265 | 266 | if (!state.lexical.hasOwnProperty("align")) 267 | state.lexical.align = true; 268 | 269 | while(true) { 270 | var combinator = cc.length ? cc.pop() : jsonMode ? expression : statement; 271 | if (combinator(type, content)) { 272 | while(cc.length && cc[cc.length - 1].lex) 273 | cc.pop()(); 274 | if (cx.marked) return cx.marked; 275 | if (type == "variable" && inScope(state, content)) return "variable-2"; 276 | return style; 277 | } 278 | } 279 | } 280 | 281 | // Combinator utils 282 | 283 | var cx = {state: null, column: null, marked: null, cc: null}; 284 | function pass() { 285 | for (var i = arguments.length - 1; i >= 0; i--) cx.cc.push(arguments[i]); 286 | } 287 | function cont() { 288 | pass.apply(null, arguments); 289 | return true; 290 | } 291 | function register(varname) { 292 | function inList(list) { 293 | for (var v = list; v; v = v.next) 294 | if (v.name == varname) return true; 295 | return false; 296 | } 297 | var state = cx.state; 298 | cx.marked = "def"; 299 | if (state.context) { 300 | if (inList(state.localVars)) return; 301 | state.localVars = {name: varname, next: state.localVars}; 302 | } else { 303 | if (inList(state.globalVars)) return; 304 | if (parserConfig.globalVars) 305 | state.globalVars = {name: varname, next: state.globalVars}; 306 | } 307 | } 308 | 309 | // Combinators 310 | 311 | var defaultVars = {name: "this", next: {name: "arguments"}}; 312 | function pushcontext() { 313 | cx.state.context = {prev: cx.state.context, vars: cx.state.localVars}; 314 | cx.state.localVars = defaultVars; 315 | } 316 | function popcontext() { 317 | cx.state.localVars = cx.state.context.vars; 318 | cx.state.context = cx.state.context.prev; 319 | } 320 | function pushlex(type, info) { 321 | var result = function() { 322 | var state = cx.state, indent = state.indented; 323 | if (state.lexical.type == "stat") indent = state.lexical.indented; 324 | else for (var outer = state.lexical; outer && outer.type == ")" && outer.align; outer = outer.prev) 325 | indent = outer.indented; 326 | state.lexical = new JSLexical(indent, cx.stream.column(), type, null, state.lexical, info); 327 | }; 328 | result.lex = true; 329 | return result; 330 | } 331 | function poplex() { 332 | var state = cx.state; 333 | if (state.lexical.prev) { 334 | if (state.lexical.type == ")") 335 | state.indented = state.lexical.indented; 336 | state.lexical = state.lexical.prev; 337 | } 338 | } 339 | poplex.lex = true; 340 | 341 | function expect(wanted) { 342 | function exp(type) { 343 | if (type == wanted) return cont(); 344 | else if (wanted == ";") return pass(); 345 | else return cont(exp); 346 | }; 347 | return exp; 348 | } 349 | 350 | function statement(type, value) { 351 | if (type == "var") return cont(pushlex("vardef", value.length), vardef, expect(";"), poplex); 352 | if (type == "keyword a") return cont(pushlex("form"), parenExpr, statement, poplex); 353 | if (type == "keyword b") return cont(pushlex("form"), statement, poplex); 354 | if (type == "{") return cont(pushlex("}"), block, poplex); 355 | if (type == ";") return cont(); 356 | if (type == "if") { 357 | if (cx.state.lexical.info == "else" && cx.state.cc[cx.state.cc.length - 1] == poplex) 358 | cx.state.cc.pop()(); 359 | return cont(pushlex("form"), parenExpr, statement, poplex, maybeelse); 360 | } 361 | if (type == "function") return cont(functiondef); 362 | if (type == "for") return cont(pushlex("form"), forspec, statement, poplex); 363 | if (type == "variable") { 364 | if (isTS && value == "type") { 365 | cx.marked = "keyword" 366 | return cont(typeexpr, expect("operator"), typeexpr, expect(";")); 367 | } else { 368 | return cont(pushlex("stat"), maybelabel); 369 | } 370 | } 371 | if (type == "switch") return cont(pushlex("form"), parenExpr, expect("{"), pushlex("}", "switch"), 372 | block, poplex, poplex); 373 | if (type == "case") return cont(expression, expect(":")); 374 | if (type == "default") return cont(expect(":")); 375 | if (type == "catch") return cont(pushlex("form"), pushcontext, expect("("), funarg, expect(")"), 376 | statement, poplex, popcontext); 377 | if (type == "class") return cont(pushlex("form"), className, poplex); 378 | if (type == "export") return cont(pushlex("stat"), afterExport, poplex); 379 | if (type == "import") return cont(pushlex("stat"), afterImport, poplex); 380 | if (type == "module") return cont(pushlex("form"), pattern, expect("{"), pushlex("}"), block, poplex, poplex) 381 | if (type == "async") return cont(statement) 382 | if (value == "@") return cont(expression, statement) 383 | return pass(pushlex("stat"), expression, expect(";"), poplex); 384 | } 385 | function expression(type) { 386 | return expressionInner(type, false); 387 | } 388 | function expressionNoComma(type) { 389 | return expressionInner(type, true); 390 | } 391 | function parenExpr(type) { 392 | if (type != "(") return pass() 393 | return cont(pushlex(")"), expression, expect(")"), poplex) 394 | } 395 | function expressionInner(type, noComma) { 396 | if (cx.state.fatArrowAt == cx.stream.start) { 397 | var body = noComma ? arrowBodyNoComma : arrowBody; 398 | if (type == "(") return cont(pushcontext, pushlex(")"), commasep(pattern, ")"), poplex, expect("=>"), body, popcontext); 399 | else if (type == "variable") return pass(pushcontext, pattern, expect("=>"), body, popcontext); 400 | } 401 | 402 | var maybeop = noComma ? maybeoperatorNoComma : maybeoperatorComma; 403 | if (atomicTypes.hasOwnProperty(type)) return cont(maybeop); 404 | if (type == "function") return cont(functiondef, maybeop); 405 | if (type == "class") return cont(pushlex("form"), classExpression, poplex); 406 | if (type == "keyword c" || type == "async") return cont(noComma ? maybeexpressionNoComma : maybeexpression); 407 | if (type == "(") return cont(pushlex(")"), maybeexpression, expect(")"), poplex, maybeop); 408 | if (type == "operator" || type == "spread") return cont(noComma ? expressionNoComma : expression); 409 | if (type == "[") return cont(pushlex("]"), arrayLiteral, poplex, maybeop); 410 | if (type == "{") return contCommasep(objprop, "}", null, maybeop); 411 | if (type == "quasi") return pass(quasi, maybeop); 412 | if (type == "new") return cont(maybeTarget(noComma)); 413 | return cont(); 414 | } 415 | function maybeexpression(type) { 416 | if (type.match(/[;\}\)\],]/)) return pass(); 417 | return pass(expression); 418 | } 419 | function maybeexpressionNoComma(type) { 420 | if (type.match(/[;\}\)\],]/)) return pass(); 421 | return pass(expressionNoComma); 422 | } 423 | 424 | function maybeoperatorComma(type, value) { 425 | if (type == ",") return cont(expression); 426 | return maybeoperatorNoComma(type, value, false); 427 | } 428 | function maybeoperatorNoComma(type, value, noComma) { 429 | var me = noComma == false ? maybeoperatorComma : maybeoperatorNoComma; 430 | var expr = noComma == false ? expression : expressionNoComma; 431 | if (type == "=>") return cont(pushcontext, noComma ? arrowBodyNoComma : arrowBody, popcontext); 432 | if (type == "operator") { 433 | if (/\+\+|--/.test(value)) return cont(me); 434 | if (value == "?") return cont(expression, expect(":"), expr); 435 | return cont(expr); 436 | } 437 | if (type == "quasi") { return pass(quasi, me); } 438 | if (type == ";") return; 439 | if (type == "(") return contCommasep(expressionNoComma, ")", "call", me); 440 | if (type == ".") return cont(property, me); 441 | if (type == "[") return cont(pushlex("]"), maybeexpression, expect("]"), poplex, me); 442 | } 443 | function quasi(type, value) { 444 | if (type != "quasi") return pass(); 445 | if (value.slice(value.length - 2) != "${") return cont(quasi); 446 | return cont(expression, continueQuasi); 447 | } 448 | function continueQuasi(type) { 449 | if (type == "}") { 450 | cx.marked = "string-2"; 451 | cx.state.tokenize = tokenQuasi; 452 | return cont(quasi); 453 | } 454 | } 455 | function arrowBody(type) { 456 | findFatArrow(cx.stream, cx.state); 457 | return pass(type == "{" ? statement : expression); 458 | } 459 | function arrowBodyNoComma(type) { 460 | findFatArrow(cx.stream, cx.state); 461 | return pass(type == "{" ? statement : expressionNoComma); 462 | } 463 | function maybeTarget(noComma) { 464 | return function(type) { 465 | if (type == ".") return cont(noComma ? targetNoComma : target); 466 | else return pass(noComma ? expressionNoComma : expression); 467 | }; 468 | } 469 | function target(_, value) { 470 | if (value == "target") { cx.marked = "keyword"; return cont(maybeoperatorComma); } 471 | } 472 | function targetNoComma(_, value) { 473 | if (value == "target") { cx.marked = "keyword"; return cont(maybeoperatorNoComma); } 474 | } 475 | function maybelabel(type) { 476 | if (type == ":") return cont(poplex, statement); 477 | return pass(maybeoperatorComma, expect(";"), poplex); 478 | } 479 | function property(type) { 480 | if (type == "variable") {cx.marked = "property"; return cont();} 481 | } 482 | function objprop(type, value) { 483 | if (type == "async") { 484 | cx.marked = "property"; 485 | return cont(objprop); 486 | } else if (type == "variable" || cx.style == "keyword") { 487 | cx.marked = "property"; 488 | if (value == "get" || value == "set") return cont(getterSetter); 489 | return cont(afterprop); 490 | } else if (type == "number" || type == "string") { 491 | cx.marked = jsonldMode ? "property" : (cx.style + " property"); 492 | return cont(afterprop); 493 | } else if (type == "jsonld-keyword") { 494 | return cont(afterprop); 495 | } else if (type == "modifier") { 496 | return cont(objprop) 497 | } else if (type == "[") { 498 | return cont(expression, expect("]"), afterprop); 499 | } else if (type == "spread") { 500 | return cont(expression); 501 | } else if (type == ":") { 502 | return pass(afterprop) 503 | } 504 | } 505 | function getterSetter(type) { 506 | if (type != "variable") return pass(afterprop); 507 | cx.marked = "property"; 508 | return cont(functiondef); 509 | } 510 | function afterprop(type) { 511 | if (type == ":") return cont(expressionNoComma); 512 | if (type == "(") return pass(functiondef); 513 | } 514 | function commasep(what, end, sep) { 515 | function proceed(type, value) { 516 | if (sep ? sep.indexOf(type) > -1 : type == ",") { 517 | var lex = cx.state.lexical; 518 | if (lex.info == "call") lex.pos = (lex.pos || 0) + 1; 519 | return cont(function(type, value) { 520 | if (type == end || value == end) return pass() 521 | return pass(what) 522 | }, proceed); 523 | } 524 | if (type == end || value == end) return cont(); 525 | return cont(expect(end)); 526 | } 527 | return function(type, value) { 528 | if (type == end || value == end) return cont(); 529 | return pass(what, proceed); 530 | }; 531 | } 532 | function contCommasep(what, end, info) { 533 | for (var i = 3; i < arguments.length; i++) 534 | cx.cc.push(arguments[i]); 535 | return cont(pushlex(end, info), commasep(what, end), poplex); 536 | } 537 | function block(type) { 538 | if (type == "}") return cont(); 539 | return pass(statement, block); 540 | } 541 | function maybetype(type, value) { 542 | if (isTS) { 543 | if (type == ":") return cont(typeexpr); 544 | if (value == "?") return cont(maybetype); 545 | } 546 | } 547 | function typeexpr(type) { 548 | if (type == "variable") {cx.marked = "variable-3"; return cont(afterType);} 549 | if (type == "string" || type == "number" || type == "atom") return cont(afterType); 550 | if (type == "{") return cont(pushlex("}"), commasep(typeprop, "}", ",;"), poplex, afterType) 551 | if (type == "(") return cont(commasep(typearg, ")"), maybeReturnType) 552 | } 553 | function maybeReturnType(type) { 554 | if (type == "=>") return cont(typeexpr) 555 | } 556 | function typeprop(type, value) { 557 | if (type == "variable" || cx.style == "keyword") { 558 | cx.marked = "property" 559 | return cont(typeprop) 560 | } else if (value == "?") { 561 | return cont(typeprop) 562 | } else if (type == ":") { 563 | return cont(typeexpr) 564 | } else if (type == "[") { 565 | return cont(expression, maybetype, expect("]"), typeprop) 566 | } 567 | } 568 | function typearg(type) { 569 | if (type == "variable") return cont(typearg) 570 | else if (type == ":") return cont(typeexpr) 571 | } 572 | function afterType(type, value) { 573 | if (value == "<") return cont(pushlex(">"), commasep(typeexpr, ">"), poplex, afterType) 574 | if (value == "|" || type == ".") return cont(typeexpr) 575 | if (type == "[") return cont(expect("]"), afterType) 576 | if (value == "extends") return cont(typeexpr) 577 | } 578 | function vardef() { 579 | return pass(pattern, maybetype, maybeAssign, vardefCont); 580 | } 581 | function pattern(type, value) { 582 | if (type == "modifier") return cont(pattern) 583 | if (type == "variable") { register(value); return cont(); } 584 | if (type == "spread") return cont(pattern); 585 | if (type == "[") return contCommasep(pattern, "]"); 586 | if (type == "{") return contCommasep(proppattern, "}"); 587 | } 588 | function proppattern(type, value) { 589 | if (type == "variable" && !cx.stream.match(/^\s*:/, false)) { 590 | register(value); 591 | return cont(maybeAssign); 592 | } 593 | if (type == "variable") cx.marked = "property"; 594 | if (type == "spread") return cont(pattern); 595 | if (type == "}") return pass(); 596 | return cont(expect(":"), pattern, maybeAssign); 597 | } 598 | function maybeAssign(_type, value) { 599 | if (value == "=") return cont(expressionNoComma); 600 | } 601 | function vardefCont(type) { 602 | if (type == ",") return cont(vardef); 603 | } 604 | function maybeelse(type, value) { 605 | if (type == "keyword b" && value == "else") return cont(pushlex("form", "else"), statement, poplex); 606 | } 607 | function forspec(type) { 608 | if (type == "(") return cont(pushlex(")"), forspec1, expect(")"), poplex); 609 | } 610 | function forspec1(type) { 611 | if (type == "var") return cont(vardef, expect(";"), forspec2); 612 | if (type == ";") return cont(forspec2); 613 | if (type == "variable") return cont(formaybeinof); 614 | return pass(expression, expect(";"), forspec2); 615 | } 616 | function formaybeinof(_type, value) { 617 | if (value == "in" || value == "of") { cx.marked = "keyword"; return cont(expression); } 618 | return cont(maybeoperatorComma, forspec2); 619 | } 620 | function forspec2(type, value) { 621 | if (type == ";") return cont(forspec3); 622 | if (value == "in" || value == "of") { cx.marked = "keyword"; return cont(expression); } 623 | return pass(expression, expect(";"), forspec3); 624 | } 625 | function forspec3(type) { 626 | if (type != ")") cont(expression); 627 | } 628 | function functiondef(type, value) { 629 | if (value == "*") {cx.marked = "keyword"; return cont(functiondef);} 630 | if (type == "variable") {register(value); return cont(functiondef);} 631 | if (type == "(") return cont(pushcontext, pushlex(")"), commasep(funarg, ")"), poplex, maybetype, statement, popcontext); 632 | if (isTS && value == "<") return cont(pushlex(">"), commasep(typeexpr, ">"), poplex, functiondef) 633 | } 634 | function funarg(type) { 635 | if (type == "spread") return cont(funarg); 636 | return pass(pattern, maybetype, maybeAssign); 637 | } 638 | function classExpression(type, value) { 639 | // Class expressions may have an optional name. 640 | if (type == "variable") return className(type, value); 641 | return classNameAfter(type, value); 642 | } 643 | function className(type, value) { 644 | if (type == "variable") {register(value); return cont(classNameAfter);} 645 | } 646 | function classNameAfter(type, value) { 647 | if (value == "<") return cont(pushlex(">"), commasep(typeexpr, ">"), poplex, classNameAfter) 648 | if (value == "extends" || value == "implements" || (isTS && type == ",")) 649 | return cont(isTS ? typeexpr : expression, classNameAfter); 650 | if (type == "{") return cont(pushlex("}"), classBody, poplex); 651 | } 652 | function classBody(type, value) { 653 | if (type == "variable" || cx.style == "keyword") { 654 | if ((value == "async" || value == "static" || value == "get" || value == "set" || 655 | (isTS && (value == "public" || value == "private" || value == "protected" || value == "readonly" || value == "abstract"))) && 656 | cx.stream.match(/^\s+[\w$\xa1-\uffff]/, false)) { 657 | cx.marked = "keyword"; 658 | return cont(classBody); 659 | } 660 | cx.marked = "property"; 661 | return cont(isTS ? classfield : functiondef, classBody); 662 | } 663 | if (type == "[") 664 | return cont(expression, expect("]"), isTS ? classfield : functiondef, classBody) 665 | if (value == "*") { 666 | cx.marked = "keyword"; 667 | return cont(classBody); 668 | } 669 | if (type == ";") return cont(classBody); 670 | if (type == "}") return cont(); 671 | if (value == "@") return cont(expression, classBody) 672 | } 673 | function classfield(type, value) { 674 | if (value == "?") return cont(classfield) 675 | if (type == ":") return cont(typeexpr, maybeAssign) 676 | if (value == "=") return cont(expressionNoComma) 677 | return pass(functiondef) 678 | } 679 | function afterExport(type, value) { 680 | if (value == "*") { cx.marked = "keyword"; return cont(maybeFrom, expect(";")); } 681 | if (value == "default") { cx.marked = "keyword"; return cont(expression, expect(";")); } 682 | if (type == "{") return cont(commasep(exportField, "}"), maybeFrom, expect(";")); 683 | return pass(statement); 684 | } 685 | function exportField(type, value) { 686 | if (value == "as") { cx.marked = "keyword"; return cont(expect("variable")); } 687 | if (type == "variable") return pass(expressionNoComma, exportField); 688 | } 689 | function afterImport(type) { 690 | if (type == "string") return cont(); 691 | return pass(importSpec, maybeMoreImports, maybeFrom); 692 | } 693 | function importSpec(type, value) { 694 | if (type == "{") return contCommasep(importSpec, "}"); 695 | if (type == "variable") register(value); 696 | if (value == "*") cx.marked = "keyword"; 697 | return cont(maybeAs); 698 | } 699 | function maybeMoreImports(type) { 700 | if (type == ",") return cont(importSpec, maybeMoreImports) 701 | } 702 | function maybeAs(_type, value) { 703 | if (value == "as") { cx.marked = "keyword"; return cont(importSpec); } 704 | } 705 | function maybeFrom(_type, value) { 706 | if (value == "from") { cx.marked = "keyword"; return cont(expression); } 707 | } 708 | function arrayLiteral(type) { 709 | if (type == "]") return cont(); 710 | return pass(commasep(expressionNoComma, "]")); 711 | } 712 | 713 | function isContinuedStatement(state, textAfter) { 714 | return state.lastType == "operator" || state.lastType == "," || 715 | isOperatorChar.test(textAfter.charAt(0)) || 716 | /[,.]/.test(textAfter.charAt(0)); 717 | } 718 | 719 | // Interface 720 | 721 | return { 722 | startState: function(basecolumn) { 723 | var state = { 724 | tokenize: tokenBase, 725 | lastType: "sof", 726 | cc: [], 727 | lexical: new JSLexical((basecolumn || 0) - indentUnit, 0, "block", false), 728 | localVars: parserConfig.localVars, 729 | context: parserConfig.localVars && {vars: parserConfig.localVars}, 730 | indented: basecolumn || 0 731 | }; 732 | if (parserConfig.globalVars && typeof parserConfig.globalVars == "object") 733 | state.globalVars = parserConfig.globalVars; 734 | return state; 735 | }, 736 | 737 | token: function(stream, state) { 738 | if (stream.sol()) { 739 | if (!state.lexical.hasOwnProperty("align")) 740 | state.lexical.align = false; 741 | state.indented = stream.indentation(); 742 | findFatArrow(stream, state); 743 | } 744 | if (state.tokenize != tokenComment && stream.eatSpace()) return null; 745 | var style = state.tokenize(stream, state); 746 | if (type == "comment") return style; 747 | state.lastType = type == "operator" && (content == "++" || content == "--") ? "incdec" : type; 748 | return parseJS(state, style, type, content, stream); 749 | }, 750 | 751 | indent: function(state, textAfter) { 752 | if (state.tokenize == tokenComment) return CodeMirror.Pass; 753 | if (state.tokenize != tokenBase) return 0; 754 | var firstChar = textAfter && textAfter.charAt(0), lexical = state.lexical, top 755 | // Kludge to prevent 'maybelse' from blocking lexical scope pops 756 | if (!/^\s*else\b/.test(textAfter)) for (var i = state.cc.length - 1; i >= 0; --i) { 757 | var c = state.cc[i]; 758 | if (c == poplex) lexical = lexical.prev; 759 | else if (c != maybeelse) break; 760 | } 761 | while ((lexical.type == "stat" || lexical.type == "form") && 762 | (firstChar == "}" || ((top = state.cc[state.cc.length - 1]) && 763 | (top == maybeoperatorComma || top == maybeoperatorNoComma) && 764 | !/^[,\.=+\-*:?[\(]/.test(textAfter)))) 765 | lexical = lexical.prev; 766 | if (statementIndent && lexical.type == ")" && lexical.prev.type == "stat") 767 | lexical = lexical.prev; 768 | var type = lexical.type, closing = firstChar == type; 769 | 770 | if (type == "vardef") return lexical.indented + (state.lastType == "operator" || state.lastType == "," ? lexical.info + 1 : 0); 771 | else if (type == "form" && firstChar == "{") return lexical.indented; 772 | else if (type == "form") return lexical.indented + indentUnit; 773 | else if (type == "stat") 774 | return lexical.indented + (isContinuedStatement(state, textAfter) ? statementIndent || indentUnit : 0); 775 | else if (lexical.info == "switch" && !closing && parserConfig.doubleIndentSwitch != false) 776 | return lexical.indented + (/^(?:case|default)\b/.test(textAfter) ? indentUnit : 2 * indentUnit); 777 | else if (lexical.align) return lexical.column + (closing ? 0 : 1); 778 | else return lexical.indented + (closing ? 0 : indentUnit); 779 | }, 780 | 781 | electricInput: /^\s*(?:case .*?:|default:|\{|\})$/, 782 | blockCommentStart: jsonMode ? null : "/*", 783 | blockCommentEnd: jsonMode ? null : "*/", 784 | lineComment: jsonMode ? null : "//", 785 | fold: "brace", 786 | closeBrackets: "()[]{}''\"\"``", 787 | 788 | helperType: jsonMode ? "json" : "javascript", 789 | jsonldMode: jsonldMode, 790 | jsonMode: jsonMode, 791 | 792 | expressionAllowed: expressionAllowed, 793 | skipExpression: function(state) { 794 | var top = state.cc[state.cc.length - 1] 795 | if (top == expression || top == expressionNoComma) state.cc.pop() 796 | } 797 | }; 798 | }); 799 | 800 | CodeMirror.registerHelper("wordChars", "javascript", /[\w$]/); 801 | 802 | CodeMirror.defineMIME("text/javascript", "javascript"); 803 | CodeMirror.defineMIME("text/ecmascript", "javascript"); 804 | CodeMirror.defineMIME("application/javascript", "javascript"); 805 | CodeMirror.defineMIME("application/x-javascript", "javascript"); 806 | CodeMirror.defineMIME("application/ecmascript", "javascript"); 807 | CodeMirror.defineMIME("application/json", {name: "javascript", json: true}); 808 | CodeMirror.defineMIME("application/x-json", {name: "javascript", json: true}); 809 | CodeMirror.defineMIME("application/ld+json", {name: "javascript", jsonld: true}); 810 | CodeMirror.defineMIME("text/typescript", { name: "javascript", typescript: true }); 811 | CodeMirror.defineMIME("application/typescript", { name: "javascript", typescript: true }); 812 | 813 | }); 814 | -------------------------------------------------------------------------------- /docs/javascript/json-ld.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | CodeMirror: JSON-LD mode 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 27 | 28 |
        29 |

        JSON-LD mode

        30 | 31 | 32 |
        61 | 62 | 70 | 71 |

        This is a specialization of the JavaScript mode.

        72 |
        73 | -------------------------------------------------------------------------------- /docs/javascript/typescript.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | CodeMirror: TypeScript mode 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 24 | 25 |
        26 |

        TypeScript mode

        27 | 28 | 29 |
        51 | 52 | 59 | 60 |

        This is a specialization of the JavaScript mode.

        61 |
        62 | -------------------------------------------------------------------------------- /docs/json/invalid-keys.json: -------------------------------------------------------------------------------- 1 | { 2 | "users": { 3 | "POST /user": { 4 | "headers": { 5 | "content-type": "application/json" 6 | } 7 | } 8 | } 9 | } -------------------------------------------------------------------------------- /docs/json/nested.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "shakyShane", 3 | "profile": { 4 | "links": [ 5 | { 6 | "name": "twitter", 7 | "url": "https://twitter.com/shaneOsbourne" 8 | }, 9 | { 10 | "name": "Medium", 11 | "url": "https://medium.com/@shakyShane" 12 | } 13 | ] 14 | }, 15 | "location": null, 16 | "email": null, 17 | "bio": null, 18 | "public_repos": 145, 19 | "public_gists": 69, 20 | "followers": 298, 21 | "following": 1, 22 | "created_at": "2012-04-14T17:34:37Z", 23 | "updated_at": "2017-04-26T12:43:35Z" 24 | } -------------------------------------------------------------------------------- /docs/json/optional.json: -------------------------------------------------------------------------------- 1 | { 2 | "users": [ 3 | { 4 | "name": "shane", 5 | "age": "32" 6 | }, 7 | { 8 | "name": "sally", 9 | "age": "29", 10 | "pets": [{ 11 | "type": "cat", 12 | "name": "kittie" 13 | }] 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /docs/json/recursive.json: -------------------------------------------------------------------------------- 1 | { 2 | "items": [ 3 | { 4 | "body": [ 5 | { 6 | "body": [ 7 | { 8 | "body": [] 9 | } 10 | ] 11 | } 12 | ] 13 | } 14 | ] 15 | } -------------------------------------------------------------------------------- /docs/lib/codemirror.css: -------------------------------------------------------------------------------- 1 | /* BASICS */ 2 | 3 | .CodeMirror { 4 | /* Set height, width, borders, and global font properties here */ 5 | font-family: monospace; 6 | height: 100vh; 7 | color: black; 8 | } 9 | 10 | /* PADDING */ 11 | 12 | .CodeMirror-lines { 13 | padding: 1em 0; /* Vertical padding around content */ 14 | } 15 | .CodeMirror pre { 16 | padding: 0 1em; /* Horizontal padding of content */ 17 | } 18 | 19 | .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { 20 | background-color: white; /* The little square between H and V scrollbars */ 21 | } 22 | 23 | /* GUTTER */ 24 | 25 | .CodeMirror-gutters { 26 | border-right: 1px solid #ddd; 27 | background-color: #f7f7f7; 28 | white-space: nowrap; 29 | } 30 | .CodeMirror-linenumbers {} 31 | .CodeMirror-linenumber { 32 | padding: 0 3px 0 5px; 33 | min-width: 20px; 34 | text-align: right; 35 | color: #999; 36 | white-space: nowrap; 37 | } 38 | 39 | .CodeMirror-guttermarker { color: black; } 40 | .CodeMirror-guttermarker-subtle { color: #999; } 41 | 42 | /* CURSOR */ 43 | 44 | .CodeMirror-cursor { 45 | border-left: 1px solid black; 46 | border-right: none; 47 | width: 0; 48 | } 49 | /* Shown when moving in bi-directional text */ 50 | .CodeMirror div.CodeMirror-secondarycursor { 51 | border-left: 1px solid silver; 52 | } 53 | .cm-fat-cursor .CodeMirror-cursor { 54 | width: auto; 55 | border: 0 !important; 56 | background: #7e7; 57 | } 58 | .cm-fat-cursor div.CodeMirror-cursors { 59 | z-index: 1; 60 | } 61 | 62 | .cm-animate-fat-cursor { 63 | width: auto; 64 | border: 0; 65 | -webkit-animation: blink 1.06s steps(1) infinite; 66 | -moz-animation: blink 1.06s steps(1) infinite; 67 | animation: blink 1.06s steps(1) infinite; 68 | background-color: #7e7; 69 | } 70 | @-moz-keyframes blink { 71 | 0% {} 72 | 50% { background-color: transparent; } 73 | 100% {} 74 | } 75 | @-webkit-keyframes blink { 76 | 0% {} 77 | 50% { background-color: transparent; } 78 | 100% {} 79 | } 80 | @keyframes blink { 81 | 0% {} 82 | 50% { background-color: transparent; } 83 | 100% {} 84 | } 85 | 86 | /* Can style cursor different in overwrite (non-insert) mode */ 87 | .CodeMirror-overwrite .CodeMirror-cursor {} 88 | 89 | .cm-tab { display: inline-block; text-decoration: inherit; } 90 | 91 | .CodeMirror-rulers { 92 | position: absolute; 93 | left: 0; right: 0; top: -50px; bottom: -20px; 94 | overflow: hidden; 95 | } 96 | .CodeMirror-ruler { 97 | border-left: 1px solid #ccc; 98 | top: 0; bottom: 0; 99 | position: absolute; 100 | } 101 | 102 | /* DEFAULT THEME */ 103 | 104 | .cm-s-default .cm-header {color: blue;} 105 | .cm-s-default .cm-quote {color: #090;} 106 | .cm-negative {color: #d44;} 107 | .cm-positive {color: #292;} 108 | .cm-header, .cm-strong {font-weight: bold;} 109 | .cm-em {font-style: italic;} 110 | .cm-link {text-decoration: underline;} 111 | .cm-strikethrough {text-decoration: line-through;} 112 | 113 | .cm-s-default .cm-keyword {color: #708;} 114 | .cm-s-default .cm-atom {color: #219;} 115 | .cm-s-default .cm-number {color: #164;} 116 | .cm-s-default .cm-def {color: #00f;} 117 | .cm-s-default .cm-variable, 118 | .cm-s-default .cm-punctuation, 119 | .cm-s-default .cm-property, 120 | .cm-s-default .cm-operator {} 121 | .cm-s-default .cm-variable-2 {color: #05a;} 122 | .cm-s-default .cm-variable-3 {color: #085;} 123 | .cm-s-default .cm-comment {color: #a50;} 124 | .cm-s-default .cm-string {color: #a11;} 125 | .cm-s-default .cm-string-2 {color: #f50;} 126 | .cm-s-default .cm-meta {color: #555;} 127 | .cm-s-default .cm-qualifier {color: #555;} 128 | .cm-s-default .cm-builtin {color: #30a;} 129 | .cm-s-default .cm-bracket {color: #997;} 130 | .cm-s-default .cm-tag {color: #170;} 131 | .cm-s-default .cm-attribute {color: #00c;} 132 | .cm-s-default .cm-hr {color: #999;} 133 | .cm-s-default .cm-link {color: #00c;} 134 | 135 | .cm-s-default .cm-error {color: #f00;} 136 | .cm-invalidchar {color: #f00;} 137 | 138 | .CodeMirror-composing { border-bottom: 2px solid; } 139 | 140 | /* Default styles for common addons */ 141 | 142 | div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;} 143 | div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} 144 | .CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); } 145 | .CodeMirror-activeline-background {background: #e8f2ff;} 146 | 147 | /* STOP */ 148 | 149 | /* The rest of this file contains styles related to the mechanics of 150 | the editor. You probably shouldn't touch them. */ 151 | 152 | .CodeMirror { 153 | position: relative; 154 | overflow: hidden; 155 | background: white; 156 | } 157 | 158 | .CodeMirror-scroll { 159 | overflow: scroll !important; /* Things will break if this is overridden */ 160 | /* 30px is the magic margin used to hide the element's real scrollbars */ 161 | /* See overflow: hidden in .CodeMirror */ 162 | margin-bottom: -30px; margin-right: -30px; 163 | padding-bottom: 30px; 164 | height: 100%; 165 | outline: none; /* Prevent dragging from highlighting the element */ 166 | position: relative; 167 | } 168 | .CodeMirror-sizer { 169 | position: relative; 170 | border-right: 30px solid transparent; 171 | } 172 | 173 | /* The fake, visible scrollbars. Used to force redraw during scrolling 174 | before actual scrolling happens, thus preventing shaking and 175 | flickering artifacts. */ 176 | .CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { 177 | position: absolute; 178 | z-index: 6; 179 | display: none; 180 | } 181 | .CodeMirror-vscrollbar { 182 | right: 0; top: 0; 183 | overflow-x: hidden; 184 | overflow-y: scroll; 185 | } 186 | .CodeMirror-hscrollbar { 187 | bottom: 0; left: 0; 188 | overflow-y: hidden; 189 | overflow-x: scroll; 190 | } 191 | .CodeMirror-scrollbar-filler { 192 | right: 0; bottom: 0; 193 | } 194 | .CodeMirror-gutter-filler { 195 | left: 0; bottom: 0; 196 | } 197 | 198 | .CodeMirror-gutters { 199 | position: absolute; left: 0; top: 0; 200 | min-height: 100%; 201 | z-index: 3; 202 | } 203 | .CodeMirror-gutter { 204 | white-space: normal; 205 | height: 100%; 206 | display: inline-block; 207 | vertical-align: top; 208 | margin-bottom: -30px; 209 | } 210 | .CodeMirror-gutter-wrapper { 211 | position: absolute; 212 | z-index: 4; 213 | background: none !important; 214 | border: none !important; 215 | } 216 | .CodeMirror-gutter-background { 217 | position: absolute; 218 | top: 0; bottom: 0; 219 | z-index: 4; 220 | } 221 | .CodeMirror-gutter-elt { 222 | position: absolute; 223 | cursor: default; 224 | z-index: 4; 225 | } 226 | .CodeMirror-gutter-wrapper ::selection { background-color: transparent } 227 | .CodeMirror-gutter-wrapper ::-moz-selection { background-color: transparent } 228 | 229 | .CodeMirror-lines { 230 | cursor: text; 231 | min-height: 1px; /* prevents collapsing before first draw */ 232 | } 233 | .CodeMirror pre { 234 | /* Reset some styles that the rest of the page might have set */ 235 | -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0; 236 | border-width: 0; 237 | background: transparent; 238 | font-family: inherit; 239 | font-size: inherit; 240 | margin: 0; 241 | white-space: pre; 242 | word-wrap: normal; 243 | line-height: inherit; 244 | color: inherit; 245 | z-index: 2; 246 | position: relative; 247 | overflow: visible; 248 | -webkit-tap-highlight-color: transparent; 249 | -webkit-font-variant-ligatures: contextual; 250 | font-variant-ligatures: contextual; 251 | } 252 | .CodeMirror-wrap pre { 253 | word-wrap: break-word; 254 | white-space: pre-wrap; 255 | word-break: normal; 256 | } 257 | 258 | .CodeMirror-linebackground { 259 | position: absolute; 260 | left: 0; right: 0; top: 0; bottom: 0; 261 | z-index: 0; 262 | } 263 | 264 | .CodeMirror-linewidget { 265 | position: relative; 266 | z-index: 2; 267 | overflow: auto; 268 | } 269 | 270 | .CodeMirror-widget {} 271 | 272 | .CodeMirror-rtl pre { direction: rtl; } 273 | 274 | .CodeMirror-code { 275 | outline: none; 276 | } 277 | 278 | /* Force content-box sizing for the elements where we expect it */ 279 | .CodeMirror-scroll, 280 | .CodeMirror-sizer, 281 | .CodeMirror-gutter, 282 | .CodeMirror-gutters, 283 | .CodeMirror-linenumber { 284 | -moz-box-sizing: content-box; 285 | box-sizing: content-box; 286 | } 287 | 288 | .CodeMirror-measure { 289 | position: absolute; 290 | width: 100%; 291 | height: 0; 292 | overflow: hidden; 293 | visibility: hidden; 294 | } 295 | 296 | .CodeMirror-cursor { 297 | position: absolute; 298 | pointer-events: none; 299 | } 300 | .CodeMirror-measure pre { position: static; } 301 | 302 | div.CodeMirror-cursors { 303 | visibility: hidden; 304 | position: relative; 305 | z-index: 3; 306 | } 307 | div.CodeMirror-dragcursors { 308 | visibility: visible; 309 | } 310 | 311 | .CodeMirror-focused div.CodeMirror-cursors { 312 | visibility: visible; 313 | } 314 | 315 | .CodeMirror-selected { background: #d9d9d9; } 316 | .CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; } 317 | .CodeMirror-crosshair { cursor: crosshair; } 318 | .CodeMirror-line::selection, .CodeMirror-line > span::selection, .CodeMirror-line > span > span::selection { background: #d7d4f0; } 319 | .CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: #d7d4f0; } 320 | 321 | .cm-searching { 322 | background: #ffa; 323 | background: rgba(255, 255, 0, .4); 324 | } 325 | 326 | /* Used to force a border model for a node */ 327 | .cm-force-border { padding-right: .1px; } 328 | 329 | @media print { 330 | /* Hide the cursor when printing */ 331 | .CodeMirror div.CodeMirror-cursors { 332 | visibility: hidden; 333 | } 334 | } 335 | 336 | /* See issue #2901 */ 337 | .cm-tab-wrap-hack:after { content: ''; } 338 | 339 | /* Help users use markselection to safely style text background */ 340 | span.CodeMirror-selectedtext { background: none; } 341 | -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "unfetch": "^2.1.2", 4 | "webpack": "^2.6.1" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /docs/src/index.js: -------------------------------------------------------------------------------- 1 | const fetch = require('unfetch').default; 2 | 3 | const iniialJson = getHash() || `{ 4 | "author": "shakyShane", 5 | "profile": { 6 | "links": [ 7 | { 8 | "name": "twitter", 9 | "url": "https://twitter.com/shaneOsbourne" 10 | }, 11 | { 12 | "name": "Medium", 13 | "url": "https://medium.com/@shakyShane" 14 | } 15 | ] 16 | }, 17 | "location": null, 18 | "email": null, 19 | "bio": null, 20 | "public_repos": 145, 21 | "public_gists": 69, 22 | "followers": 298, 23 | "following": 1, 24 | "created_at": "2012-04-14T17:34:37Z", 25 | "updated_at": "2017-04-26T12:43:35Z" 26 | }`; 27 | const $ = document.querySelector.bind(document); 28 | const statusElem = $('.status'); 29 | const statusText = $('.debug'); 30 | const flow = $('#flow'); 31 | const rootName = $('#root-name'); 32 | const prefix = $('#prefix'); 33 | const namespace = $('#namespace'); 34 | const examplesElem = $('#examples'); 35 | const examples = [ 36 | ['json/nested.json', 'Nested Objects & Arrays'], 37 | ['json/optional.json', 'Optional Fields'], 38 | ['json/recursive.json', 'Recursive data structures'], 39 | ['json/invalid-keys.json', 'Invalid property names'] 40 | ]; 41 | 42 | examples.forEach(([json, title]) => { 43 | const opt = document.createElement('option'); 44 | opt.value = json; 45 | opt.textContent = title; 46 | examplesElem.appendChild(opt); 47 | }); 48 | 49 | examplesElem.addEventListener('change', function () { 50 | if (examplesElem.value !== 'Select an example') { 51 | examplesElem.parentNode.classList.add('loading'); 52 | const resp = fetch(examplesElem.value).then(x => x.text()); 53 | resp.then(x => jsonInput.setValue(x)); 54 | } 55 | }); 56 | 57 | let defaults = { 58 | flow: false, 59 | namespace: '', 60 | prefix: 'I', 61 | rootName: 'RootObject' 62 | }; 63 | 64 | flow.addEventListener('change', function () { 65 | setOutput(jsonInput.getValue(), options({flow: flow.checked})); 66 | }); 67 | namespace.addEventListener('input', function () { 68 | setOutput(jsonInput.getValue(), options({namespace: namespace.value})); 69 | }); 70 | rootName.addEventListener('input', function () { 71 | setOutput(jsonInput.getValue(), options({rootName: rootName.value})); 72 | }); 73 | rootName.addEventListener('input', function () { 74 | setOutput(jsonInput.getValue(), options({rootName: rootName.value})); 75 | }); 76 | prefix.addEventListener('input', function () { 77 | setOutput(jsonInput.getValue(), options({prefix: prefix.value})); 78 | }); 79 | const ts = json2ts(iniialJson, defaults); 80 | 81 | var tsOutput = CodeMirror($('#ts'), { 82 | value: ts, 83 | mode: {name: "javascript", typescript: true} 84 | }); 85 | var jsonInput = CodeMirror($('#app'), { 86 | value: iniialJson, 87 | mode: {name: "javascript", json: true} 88 | }); 89 | 90 | function setOutput(json, options) { 91 | if (json && json.length > 3) { 92 | JSON.parse(json); 93 | const ts = json2ts(json, options); 94 | tsOutput.setValue(ts); 95 | status('success', 'JSON: All good!'); 96 | } 97 | } 98 | 99 | jsonInput.on('change', function (obj) { 100 | try { 101 | setOutput(jsonInput.getValue()); 102 | } catch (e) { 103 | status('error', `Error parsing JSON: ${e.message}`); 104 | console.log('Some JSON error?', e.message); 105 | } 106 | }); 107 | 108 | function getHash() { 109 | if (window.location.hash) { 110 | if (window.location.hash.slice(0, 5) === '#src=') { 111 | const maybe = window.location.hash.slice(5); 112 | if (maybe) { 113 | return decodeURIComponent(maybe); 114 | } 115 | } 116 | } 117 | return ""; 118 | } 119 | 120 | const share = $('a[href="#share"]'); 121 | share.addEventListener('click', function (e) { 122 | e.preventDefault(); 123 | window.location.hash = `src=${encodeURIComponent(jsonInput.getValue().trim())}`; 124 | }); 125 | 126 | function status(type, text) { 127 | statusElem.classList.remove('success'); 128 | statusElem.classList.remove('error'); 129 | statusElem.classList.add(type); 130 | statusText.textContent = text; 131 | } 132 | function options(incoming) { 133 | defaults = Object.assign({}, 134 | defaults, 135 | incoming 136 | ); 137 | return defaults; 138 | } 139 | -------------------------------------------------------------------------------- /json-ts2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shakyShane/json-ts/8d6195b2fa0aeaa9df7f35fa3142f5ada6c98beb/json-ts2.gif -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "json-ts", 3 | "version": "1.6.4", 4 | "description": "Automatically generate Typescript Definition files or Flow types from JSON input", 5 | "dependencies": { 6 | "get-stdin": "^5.0.1", 7 | "immutable": "^3.8.1", 8 | "minimist": "^1.2.0", 9 | "needsquotes": "^1.0.0", 10 | "transducers-js": "^0.4.174", 11 | "typescript": "^2.3.2" 12 | }, 13 | "keywords": [ 14 | "json", 15 | "typescript", 16 | "flow", 17 | "types", 18 | "interfaces" 19 | ], 20 | "scripts": { 21 | "test": "tsc && jest --runInBand", 22 | "prepublish": "npm test" 23 | }, 24 | "bin": { 25 | "json-ts": "dist/bin.js" 26 | }, 27 | "files": [ 28 | "dist", 29 | "src", 30 | "_.js" 31 | ], 32 | "repository": "shakyshane/json-ts", 33 | "main": "dist/index.js", 34 | "devDependencies": { 35 | "@types/node": "^7.0.18", 36 | "browserify": "^14.3.0", 37 | "exorcist": "^0.4.0", 38 | "jest": "22", 39 | "source-map-support": "^0.4.15", 40 | "uglify-js": "^3.0.10" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/shakyShane/json-ts.svg?branch=master)](https://travis-ci.org/shakyShane/json-ts) 2 | 3 | ## `npm install -g json-ts` 4 | 5 | > Automatically generate Typescript Definition files or Flow types from JSON input. 6 | 7 | > Use it via the API, CLI, or [Website](https://shakyshane.github.io/json-ts/) 8 | 9 | ![json-ts](https://cdn.rawgit.com/shakyShane/json-ts/37ce9b2b/json-ts2.gif) 10 | 11 | ### How does **json-ts** stack up against the competition? 12 | 13 | |Feature |json-ts (this library) |[json-to-ts](https://github.com/MariusAlch/json-to-ts) |[json2ts](http://json2ts.com/) | 14 | |---|---|---|---| 15 | |simple literal types (number, string etc) |**YES** |YES |YES | 16 | |array type, all elements of same kind |**YES** |YES |YES | 17 | |merge multiple json files|**YES (cli, v1.6 & above)** |NO |NO | 18 | |optional members | **YES** | YES | NO | 19 | |array union types | **YES** |YES |NO | 20 | |correct handling of top-level values ([strings](https://shakyshane.github.io/json-ts/#src=%22some-api-token-as-string%22), [arrays](https://shakyshane.github.io/json-ts/#src=%5B1%2C%202%2C%203%5D), [arrays of objects](https://shakyshane.github.io/json-ts/#src=%5B%0A%20%20%7B%22name%22%3A%20%22shane%22%7D%2C%0A%20%20%7B%22name%22%3A%20%22kittie%22%2C%20%22age%22%3A%2030%7D%0A%5D), [numbers](https://shakyshane.github.io/json-ts/#src=1) etc) |**YES** |NO |NO | 21 | |recursive data structures ([see here](https://github.com/shakyShane/json-ts/blob/master/__tests__/magento/categories.json)) |**YES** |NO |NO | 22 | |nested type literals (to account for invalid [interface names](https://github.com/shakyShane/json-ts/blob/master/__tests__/swagger/schema.json)) |**YES** |YES |NO | 23 | |output @flow types |**YES** |NO |NO | 24 | |Website |**[YES](https://shakyshane.github.io/json-ts/)** |[YES](http://www.jsontots.com/) |[YES](http://json2ts.com/) | 25 | |CLI |**YES** |NO |NO | 26 | |API |**YES** |YES |NO | 27 | 28 | ## Quick-start 29 | ```bash 30 | # install 31 | npm install -g json-ts 32 | 33 | # run against a single JSON file 34 | json-ts dir/myfile.json 35 | 36 | # run against multiple single JSON files (interfaces will be merged) 37 | json-ts api/resp1.json api/resp2.json 38 | ``` 39 | 40 | ## Usage (CLI) 41 | Note: only stdin (which requires the --stdin flag) & filepaths are supported right now. 42 | Later I will add support for Windows, reading data from network requests etc. 43 | 44 | ```bash 45 | ## piping via stdin 46 | curl https://jsonplaceholder.typicode.com/posts/1 | json-ts --stdin 47 | 48 | ## reading single json file from disk 49 | json-ts my-file.json 50 | 51 | ## reading multiple json files from disk 52 | json-ts my-file.json my-other-file.json 53 | ``` 54 | 55 | ... produces the following: 56 | 57 | ```ts 58 | interface IRootObject { 59 | userId: number; 60 | id: number; 61 | title: string; 62 | body: string; 63 | } 64 | ``` 65 | 66 | ## Usage (API) 67 | 68 | ```bash 69 | npm install json-ts --save-dev 70 | ``` 71 | 72 | ```js 73 | const { json2ts } = require('json-ts'); 74 | const json = ` 75 | { 76 | "name": "Shane" 77 | } 78 | `; 79 | console.log(json2ts(json)) 80 | ``` 81 | 82 | ... produces the following: 83 | 84 | ```ts 85 | interface IRootObject { 86 | name: string; 87 | } 88 | ``` 89 | 90 | For more examples, see the [Tests](https://github.com/shakyShane/json-ts/tree/master/__tests__) 91 | 92 | ## Options 93 | 94 | - **namespace: string** - if provided, interfaces will be wrapped in a namespace (see below) 95 | ```bash 96 | # usage 97 | json-ts --namespace 98 | 99 | # example 100 | json-ts data/my-file.json --namespace API 101 | ``` 102 | - **flow: boolean** - output types in Flow format. 103 | ```bash 104 | # usage 105 | json-ts --flow 106 | 107 | # example 108 | json-ts data/my-file.json --flow 109 | ``` 110 | - **prefix: string** - override the `I` prefix on interface names 111 | ```bash 112 | # usage 113 | json-ts --prefix 114 | 115 | # example (remove prefix) 116 | json-ts data/my-file.json --prefix "" 117 | ``` 118 | - **rootName: string** - override the `RootObject` name of the top-level interface 119 | ```bash 120 | # usage 121 | json-ts --rootName 122 | 123 | # example 124 | json-ts data/my-file.json --rootName "Product" 125 | ``` 126 | 127 | ## TODO: 128 | 129 | ### options 130 | 131 | - [x] Allow choice of `I` prefix on interface names 132 | - [x] Allow naming of RootObject 133 | - [ ] Allow choice of spaces/tabs 134 | 135 | ### Core 136 | - [x] support array types 137 | - [x] support boolean types 138 | - [x] support null types 139 | - [x] output types for Flow via `--flow` 140 | - [x] PascalCase as default for all interface names 141 | - [x] de-dupe interfaces (it's dumb atm, POC) 142 | - [x] de-dupe interfaces where propname differs, but members are the same 143 | - [x] merge interfaces by creating union types for members 144 | - [x] union types for array that contain mixed literal types: `nums: [1, "2"] -> nums: number|string[]` 145 | (already works for complex objects) 146 | - [x] quoted member names when needed 147 | - [x] handle invalid name for interface 148 | - [x] support type alias declarations 149 | - [x] Use Typescript factory methods/printer for output 150 | - [x] Allow wrapping in namespace: eg: 151 | ```ts 152 | declare namespace Projects { 153 | export interface ILoc { 154 | lat: number; 155 | lng: number; 156 | } 157 | ... 158 | } 159 | ``` 160 | 161 | ### CLI 162 | - [x] CLI tool to accept stdin (with `--stdin` flag) 163 | - [x] CLI tool to accept json file as input 164 | - [ ] CLI tool to accept URL as input (for validating against remote API) 165 | - [ ] configurable output (filename/stdout etc) 166 | -------------------------------------------------------------------------------- /src/bin.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import minimist = require('minimist'); 3 | import stdin = require('get-stdin'); 4 | import {json2ts, json2tsMulti} from './'; 5 | import {fromJS, OrderedSet} from 'immutable'; 6 | import {join, parse, ParsedPath} from "path"; 7 | import {existsSync, readFile, readFileSync} from "fs"; 8 | const argv = minimist(process.argv.slice(2)); 9 | 10 | // unique input 11 | const inputs = OrderedSet(argv._); 12 | 13 | // defaults 14 | const defaults = { 15 | stdin: false, 16 | namespace: false, 17 | flow: false 18 | }; 19 | 20 | // merged options with defaults 21 | const options = { 22 | ...defaults, 23 | ...argv 24 | }; 25 | 26 | if (options.stdin) { 27 | stdin().then((str: string) => { 28 | if (str === '') { 29 | console.error('no input provided'); 30 | } else { 31 | try { 32 | JSON.parse(str); 33 | console.log(json2ts(str, options)); 34 | } catch (e) { 35 | console.error('Invalid JSON'); 36 | console.error(e.message); 37 | } 38 | } 39 | }) 40 | .catch(err => { 41 | console.error(err); 42 | }) 43 | } else { 44 | if (inputs.size === 0) { 45 | console.error('Oops! You provided no inputs'); 46 | console.log(` 47 | You can pipe JSON to this program with the --stdin flag: 48 | 49 | curl http://example.com/some-json | json-ts --stdin 50 | 51 | Or, provide path names: 52 | 53 | json-ts path/to/my-file.json 54 | `); 55 | } else { 56 | const queue = inputs 57 | .map(input => { 58 | return { 59 | input, 60 | parsed: parse(input), 61 | }; 62 | }) 63 | .map(incoming => { 64 | return { 65 | incoming, 66 | resolved: resolveInput(incoming, process.cwd()) 67 | } 68 | }); 69 | 70 | const withErrors = queue.filter(x => x.resolved.errors.length > 0); 71 | const withoutErrors = queue.filter(x => x.resolved.errors.length === 0); 72 | if (withErrors.size) { 73 | console.log('Sorry, there were errors with your input.'); 74 | withErrors.forEach(function (item) { 75 | console.log(''); 76 | console.log(` ${item.incoming.input}:`); 77 | console.log(' ', item.resolved.errors[0].error.message); 78 | }) 79 | } else { 80 | const strings = withoutErrors.map(item => { 81 | return item.resolved.content; 82 | }); 83 | console.log(json2tsMulti((strings as any), options)); 84 | } 85 | } 86 | } 87 | 88 | interface IIncomingInput { 89 | input: string, 90 | parsed: ParsedPath, 91 | } 92 | interface InputError { 93 | kind: string, 94 | error: Error 95 | } 96 | interface IResolvedInput { 97 | errors: InputError[], 98 | content?: string 99 | } 100 | 101 | function resolveInput(incoming: IIncomingInput, cwd): IResolvedInput { 102 | const absolute = join(cwd, incoming.parsed.dir, incoming.parsed.base); 103 | if (!existsSync(absolute)) { 104 | return { 105 | errors: [{ 106 | kind: 'FileNotFound', 107 | error: new Error(`File not found`) 108 | }] 109 | } 110 | } 111 | const data = readFileSync(absolute, 'utf8'); 112 | try { 113 | JSON.parse(data); 114 | return { 115 | errors: [], 116 | content: data 117 | } 118 | } catch (e) { 119 | return { 120 | errors: [{ 121 | kind: 'InvalidJson', 122 | error: e 123 | }] 124 | } 125 | } 126 | } 127 | // console.log('options:', options); 128 | // console.log('inputs:', inputs); 129 | // console.log('args', argv); 130 | -------------------------------------------------------------------------------- /src/collapse-interfaces.ts: -------------------------------------------------------------------------------- 1 | import * as ts from 'typescript'; 2 | import {namedProp} from "./transformer"; 3 | import {isEmptyArrayType, membersMatch} from "./util"; 4 | 5 | export function collapseInterfaces(interfaces: any[]): any[] { 6 | 7 | /** 8 | * { 9 | * 'IItems': {count: 5, names: Set {'pets', 'age'} } 10 | * } 11 | * @type {any} 12 | */ 13 | const memberStack = interfaces.reduce((acc, int) => { 14 | const lookup = acc[int.name.text]; 15 | if (lookup) { 16 | lookup.count += 1; 17 | int.members.forEach(mem => { 18 | lookup.names.add(mem.name.text); 19 | }) 20 | } else { 21 | acc[int.name.text] = {count: 1, names: new Set([])} 22 | } 23 | return acc; 24 | }, {}); 25 | 26 | /** 27 | * Look at each interface and mark any members absent in others 28 | * as optional. 29 | */ 30 | interfaces.forEach((i) => { 31 | const curName = i.name.text; 32 | const fromStack = memberStack[curName]; 33 | if (fromStack.count === 1) { 34 | return; 35 | } 36 | i.members.forEach(localMember => { 37 | const localName = localMember.name.text; 38 | if (!fromStack.names.has(localName)) { 39 | localMember.questionToken = ts.createNode(ts.SyntaxKind.QuestionToken); 40 | } 41 | }); 42 | }); 43 | 44 | return interfaces.reduce((accInterfaces, current) => { 45 | 46 | const currentName = current.name.text; 47 | const currentMemberNames = new Set(current.members.map(x => (x.name || x.label).text)); 48 | const matchingInterfaceIndex = accInterfaces.findIndex(x => (x.name || x.label).text === currentName); 49 | 50 | if (matchingInterfaceIndex === -1) { 51 | return accInterfaces.concat(current); 52 | } 53 | 54 | accInterfaces.forEach((int, index) => { 55 | 56 | if (index !== matchingInterfaceIndex) { 57 | return int; 58 | } 59 | 60 | const prevMemberNames = new Set(int.members.map(x => (x.name || x.label).text)); 61 | 62 | // if the current interface has less props than a previous one 63 | // we need to back-track and make the previous one optional 64 | if (currentMemberNames.size < prevMemberNames.size) { 65 | // elements that existed before, but not in the current 66 | int.members.forEach(mem => { 67 | if (!currentMemberNames.has(mem.name.text)) { 68 | mem.questionToken = ts.createNode(ts.SyntaxKind.QuestionToken); 69 | } 70 | }); 71 | } 72 | 73 | // Modify members based on missing props, union types etc 74 | modifyMembers(int.members, current.members); 75 | }); 76 | 77 | return accInterfaces; 78 | 79 | }, []); 80 | } 81 | 82 | function modifyMembers(interfaceMembers, currentMembers) { 83 | currentMembers.forEach(mem => { 84 | 85 | const existingIndex = interfaceMembers.findIndex(x => x.name.text === mem.name.text); 86 | const existingMember = interfaceMembers[existingIndex]; 87 | 88 | // Here, the current member does NOT already exist in this 89 | // interface, so we add it, but as optional 90 | if (!existingMember) { 91 | mem.questionToken = ts.createNode(ts.SyntaxKind.QuestionToken); 92 | interfaceMembers.push(mem); 93 | } else { 94 | // here it exists in both, are the types the same? 95 | // console.log(ts.SyntaxKind[mem.type.kind]); 96 | // console.log(existingMember.kind, mem.kind); 97 | if (membersMatch(existingMember, mem)) { 98 | return; 99 | } else { 100 | const updatedMember = namedProp({name: existingMember.name.text}); 101 | // const exists = existingMember.type.types.some(x => x.kind === mem.kind); 102 | 103 | // already a union, so just push a new type 104 | if (existingMember.type.kind === ts.SyntaxKind.UnionType) { 105 | const asSet = new Set(existingMember.type.types.map(x => x.kind)); 106 | if (!asSet.has(mem.type.kind)) { 107 | existingMember.type.types.push(mem.type); 108 | interfaceMembers[existingIndex] = existingMember; 109 | } 110 | } else { // not a union yet, so create one for next time around 111 | 112 | // was this previously marked as an empty array? eg: any[] 113 | // if so & the next item is NOT, then we can ignore the any[] 114 | if (isEmptyArrayType(existingMember) && !isEmptyArrayType(mem)) { 115 | updatedMember.type = ts.createNode(ts.SyntaxKind.ArrayType); 116 | updatedMember.type.elementType = mem.type.elementType; 117 | interfaceMembers[existingIndex] = updatedMember; 118 | } else { 119 | // If the INCOMING member type is an empty array, but we already have an array element with items, we bail 120 | if (isEmptyArrayType(mem) && existingMember.type.kind === ts.SyntaxKind.ArrayType && (!isEmptyArrayType(existingMember))) { 121 | return; 122 | } 123 | const memberNodes = [existingMember.type, mem.type]; 124 | updatedMember.type = ts.createUnionOrIntersectionTypeNode(ts.SyntaxKind.UnionType, memberNodes); 125 | interfaceMembers[existingIndex] = updatedMember; 126 | } 127 | } 128 | } 129 | } 130 | }); 131 | } -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import * as ts from 'typescript'; 2 | import {parse} from "./parser"; 3 | import {print, printLiteral} from "./printer"; 4 | import {transform} from "./transformer"; 5 | import {collapseInterfaces} from "./collapse-interfaces"; 6 | 7 | export interface JsonTsOptions { 8 | namespace?: string 9 | flow?: boolean 10 | prefix?: string 11 | rootName?: string 12 | } 13 | 14 | export const defaults = { 15 | prefix: "I", 16 | rootName: "RootObject" 17 | }; 18 | 19 | export function json2ts(validJsonString: string, options: JsonTsOptions = {}): string { 20 | const mergedOptions = { 21 | ...defaults, 22 | ...options 23 | }; 24 | const {stack, inputKind} = parse(validJsonString, mergedOptions); 25 | switch (inputKind) { 26 | case ts.SyntaxKind.ArrayLiteralExpression: 27 | case ts.SyntaxKind.ObjectLiteralExpression: { 28 | const transformed = transform(stack, mergedOptions); 29 | const flattened = collapseInterfaces(transformed); 30 | const printed = print(flattened, inputKind, mergedOptions); 31 | return printed; 32 | } 33 | default: { 34 | const printed = printLiteral(stack[0], inputKind, mergedOptions); 35 | return printed; 36 | } 37 | } 38 | } 39 | 40 | export function json2tsMulti(validJsonStrings: string[], options: JsonTsOptions = {}): string { 41 | const inputKinds = new Set([]); 42 | const mergedOptions = { 43 | ...defaults, 44 | ...options 45 | }; 46 | const joined = validJsonStrings.reduce((all, json) => { 47 | const {stack, inputKind} = parse(json, mergedOptions); 48 | inputKinds.add(inputKind); 49 | const transformed = transform(stack, mergedOptions); 50 | return all.concat(transformed); 51 | }, []); 52 | 53 | if (inputKinds.size > 1) { 54 | // todo handle mixed types 55 | } 56 | 57 | const flattened = collapseInterfaces(joined); 58 | const printed = print(flattened, Array.from(inputKinds)[0], mergedOptions); 59 | return printed; 60 | } 61 | 62 | export { 63 | parse, 64 | print, 65 | transform 66 | } 67 | 68 | declare var window; 69 | if ((typeof window !== 'undefined') && ((typeof window.json2ts) === 'undefined')) { 70 | window.json2ts = json2ts; 71 | window.json2ts.parse = parse; 72 | window.json2ts.transform = transform; 73 | window.json2ts.print = print; 74 | } 75 | -------------------------------------------------------------------------------- /src/parser.ts: -------------------------------------------------------------------------------- 1 | import * as ts from 'typescript'; 2 | import {JsonTsOptions} from "./index"; 3 | import needsQuotes = require("needsquotes"); 4 | 5 | export interface ParsedNode { 6 | kind: ts.SyntaxKind 7 | _kind: string 8 | name?: string 9 | value?: any 10 | body?: ParsedNode[] 11 | interfaceCandidate?: boolean 12 | } 13 | 14 | function walk(sourceFile: ts.SourceFile, initial = []): ParsedNode[] { 15 | 16 | const stack : Array = initial.slice(); 17 | const elementStack : Array = initial.slice(); 18 | 19 | function push(element) { 20 | const parent = elementStack[elementStack.length - 1]; 21 | const siblings = (parent && parent.body) ? parent.body : stack; 22 | siblings.push(element); 23 | } 24 | 25 | eachProp(sourceFile); 26 | 27 | return stack; 28 | 29 | function addFromArrayElement(incoming) { 30 | switch(incoming.kind) { 31 | case ts.SyntaxKind.NullKeyword: 32 | case ts.SyntaxKind.TrueKeyword: 33 | case ts.SyntaxKind.FalseKeyword: 34 | case ts.SyntaxKind.NumericLiteral: 35 | case ts.SyntaxKind.StringLiteral: { 36 | push(literalTypeFromArrayElement(incoming, incoming.kind)); 37 | break; 38 | } 39 | case ts.SyntaxKind.PrefixUnaryExpression: { 40 | push(literalTypeFromArrayElement(incoming, ts.SyntaxKind.NumericLiteral)); 41 | break; 42 | } 43 | case ts.SyntaxKind.ObjectLiteralExpression: { 44 | const elem = { 45 | kind: ts.SyntaxKind.ObjectLiteralExpression, 46 | _kind: `ObjectLiteralExpression`, 47 | interfaceCandidate: true, 48 | body: [], 49 | }; 50 | push(elem); 51 | elementStack.push(elem); 52 | eachProp(incoming.properties); 53 | elementStack.pop(); 54 | break; 55 | } 56 | case ts.SyntaxKind.ArrayLiteralExpression: { 57 | const elem = { 58 | kind: ts.SyntaxKind.ArrayLiteralExpression, 59 | _kind: `ArrayLiteralExpression`, 60 | body: [], 61 | }; 62 | push(elem); 63 | elementStack.push(elem); 64 | eachProp(incoming.elements); 65 | elementStack.pop(); 66 | break; 67 | } 68 | } 69 | } 70 | 71 | function eachProp(properties) { 72 | properties.forEach(function (prop) { 73 | if (!prop.initializer) { 74 | return addFromArrayElement(prop); 75 | } else { 76 | switch (prop.initializer.kind) { 77 | case ts.SyntaxKind.TrueKeyword: 78 | case ts.SyntaxKind.FalseKeyword: 79 | case ts.SyntaxKind.NullKeyword: 80 | case ts.SyntaxKind.StringLiteral: 81 | case ts.SyntaxKind.NumericLiteral: { 82 | push(literalTypeFromProp(prop, prop.initializer.kind)); 83 | break; 84 | } 85 | case ts.SyntaxKind.PrefixUnaryExpression: { 86 | push(literalTypeFromProp(prop, ts.SyntaxKind.NumericLiteral)); 87 | break; 88 | } 89 | case ts.SyntaxKind.ObjectLiteralExpression: { 90 | const quotes = needsQuotes(prop.name.text).needsQuotes; 91 | const interfaceCandidate = (quotes === false); 92 | 93 | const elem = { 94 | name: prop.name.text, 95 | body: [], 96 | interfaceCandidate, 97 | kind: ts.SyntaxKind.ObjectLiteralExpression, 98 | _kind: `ObjectLiteralExpression` 99 | }; 100 | push(elem); 101 | elementStack.push(elem); 102 | eachProp(prop.initializer.properties); 103 | elementStack.pop(); 104 | break; 105 | } 106 | case ts.SyntaxKind.ArrayLiteralExpression: { 107 | const elem = { 108 | name: prop.name.text, 109 | body: [], 110 | kind: ts.SyntaxKind.ArrayLiteralExpression, 111 | _kind: `ArrayLiteralExpression` 112 | }; 113 | push(elem); 114 | elementStack.push(elem); 115 | eachProp(prop.initializer.elements); 116 | elementStack.pop(); 117 | break; 118 | } 119 | } 120 | } 121 | }); 122 | } 123 | 124 | function literalTypeFromProp(prop, kind) { 125 | return { 126 | name: prop.name.text, 127 | value: prop.initializer.text, 128 | kind: kind, 129 | } 130 | } 131 | 132 | function literalTypeFromArrayElement(element, kind) { 133 | return { 134 | kind, 135 | name: element.text, 136 | value: element.text, 137 | } 138 | } 139 | } 140 | 141 | export function parse(string, options: JsonTsOptions): {stack: any[], inputKind: ts.SyntaxKind} { 142 | const input = `const ROOTOBJ = ${string}`; 143 | let stack; 144 | let sourceFile : ts.SourceFile = ts.createSourceFile('json.ts', input, ts.ScriptTarget.ES2015, /*setParentNodes */ true); 145 | // delint it 146 | const _json = sourceFile.statements[0] as any; 147 | const init = _json.declarationList.declarations[0].initializer; 148 | 149 | switch (init.kind) { 150 | case ts.SyntaxKind.TrueKeyword: 151 | case ts.SyntaxKind.FalseKeyword: 152 | case ts.SyntaxKind.NullKeyword: 153 | case ts.SyntaxKind.StringLiteral: 154 | case ts.SyntaxKind.NumericLiteral: { 155 | stack = [{ 156 | kind: init.kind, 157 | }]; 158 | break; 159 | } 160 | case ts.SyntaxKind.ArrayLiteralExpression: { 161 | stack = walk(init.elements, [{ 162 | kind: ts.SyntaxKind.ArrayLiteralExpression, 163 | _kind: `ArrayLiteralExpression`, 164 | name: options.rootName, 165 | body: [], 166 | }]); 167 | break; 168 | } 169 | default: stack = walk(init.properties); 170 | } 171 | 172 | return {stack, inputKind: init.kind}; 173 | } 174 | -------------------------------------------------------------------------------- /src/printer.ts: -------------------------------------------------------------------------------- 1 | import * as ts from 'typescript'; 2 | import {InterfaceNode, kindMap, log, MemberNode, namedProp} from "./transformer"; 3 | import needsQuotes = require('needsquotes'); 4 | import {JsonTsOptions} from "./index"; 5 | 6 | export function print(interfaceNodes, inputKind: ts.SyntaxKind, options: JsonTsOptions): string { 7 | 8 | const result = (ts.createSourceFile as any)('module', ''); 9 | 10 | const printer = ts.createPrinter({ 11 | newLine: ts.NewLineKind.LineFeed, 12 | }); 13 | 14 | if (inputKind === ts.SyntaxKind.ArrayLiteralExpression) { 15 | const first = interfaceNodes[0]; 16 | const newNode : any = ts.createNode(ts.SyntaxKind.TypeAliasDeclaration); 17 | newNode.type = first.members[0].type; 18 | newNode.name = ts.createIdentifier(`${options.prefix}${options.rootName}`); 19 | interfaceNodes[0] = newNode; 20 | } 21 | 22 | if (options.flow) { 23 | const modified = interfaceNodes.map(x => { 24 | const newNode : any = ts.createNode(ts.SyntaxKind.TypeAliasDeclaration); 25 | newNode.modifiers = [ts.createToken(ts.SyntaxKind.ExportKeyword)]; 26 | newNode.type = ts.createTypeLiteralNode(x.members); 27 | newNode.name = x.name; 28 | return newNode; 29 | }); 30 | 31 | const items = modified.map(x => { 32 | return printer.printNode(ts.EmitHint.Unspecified, x, result); 33 | }).join('\n') + '\n'; 34 | 35 | return ['// @flow', items].join('\n'); 36 | } 37 | 38 | if (options.namespace) { 39 | interfaceNodes.forEach(x => { 40 | x.modifiers = [ts.createToken(ts.SyntaxKind.ExportKeyword)]; 41 | }); 42 | const ns = ts.createModuleDeclaration( 43 | undefined, 44 | [ts.createToken(ts.SyntaxKind.DeclareKeyword)], 45 | ts.createIdentifier(options.namespace), 46 | ts.createModuleBlock(interfaceNodes), 47 | ts.NodeFlags.Namespace 48 | ); 49 | return printer.printNode(ts.EmitHint.Unspecified, ns, result) + '\n'; 50 | } 51 | 52 | return interfaceNodes.map(x => { 53 | return printer.printNode(ts.EmitHint.Unspecified, x, result); 54 | }) 55 | .join('\n') + '\n'; 56 | } 57 | 58 | export function printLiteral(node, kind, options) { 59 | const result = (ts.createSourceFile as any)('module', ''); 60 | 61 | const printer = ts.createPrinter({ 62 | newLine: ts.NewLineKind.LineFeed, 63 | }); 64 | 65 | const newNode : any = ts.createNode(ts.SyntaxKind.TypeAliasDeclaration); 66 | newNode.type = ts.createNode(kindMap[kind]); 67 | newNode.name = ts.createIdentifier(`${options.prefix}${options.rootName}`); 68 | 69 | return printer.printNode(ts.EmitHint.Unspecified, newNode, result); 70 | } 71 | 72 | function wrapper(blocks, options) { 73 | if (options.namespace) { 74 | const lines = [ 75 | `declare namespace ${options.namespace} {`, 76 | ...blocks.split('\n').map(x => ` ${x}`), 77 | `}`, 78 | ]; 79 | return lines.join('\n'); 80 | } 81 | return blocks; 82 | } 83 | -------------------------------------------------------------------------------- /src/transformer.ts: -------------------------------------------------------------------------------- 1 | import * as ts from 'typescript'; 2 | import {ParsedNode} from "./parser"; 3 | import {Set as ImmutableSet} from "immutable"; 4 | import needsQuotes = require('needsquotes'); 5 | import {JsonTsOptions} from "./index"; 6 | import {collapseInterfaces} from "./collapse-interfaces"; 7 | import {Node} from "typescript"; 8 | 9 | const {startCase, toLower} = require('../_'); 10 | 11 | export const log = (input) => console.log('--\n', JSON.stringify(input, null, 2)); 12 | 13 | export interface MemberNode { 14 | types: ImmutableSet 15 | members: MemberNode[] 16 | name: string 17 | optional: boolean 18 | } 19 | 20 | export interface InterfaceNode { 21 | name: string; 22 | original: string; 23 | members: MemberNode[]; 24 | } 25 | 26 | export const kindMap = { 27 | [ts.SyntaxKind.NullKeyword]: ts.SyntaxKind.NullKeyword, 28 | [ts.SyntaxKind.StringLiteral]: ts.SyntaxKind.StringKeyword, 29 | [ts.SyntaxKind.FirstLiteralToken]: ts.SyntaxKind.NumberKeyword, 30 | [ts.SyntaxKind.TrueKeyword]: ts.SyntaxKind.BooleanKeyword, 31 | [ts.SyntaxKind.FalseKeyword]: ts.SyntaxKind.BooleanKeyword, 32 | [ts.SyntaxKind.NumericLiteral]: ts.SyntaxKind.NumberKeyword, 33 | }; 34 | 35 | export function namedProp(member) { 36 | const qs = needsQuotes(member.name); 37 | 38 | const output = qs.needsQuotes ? qs.quotedValue : member.name; 39 | 40 | const prop: any = ts.createNode(ts.SyntaxKind.PropertySignature); 41 | prop.name = ts.createIdentifier(output); 42 | 43 | if (member.optional) { 44 | prop.questionToken = ts.createNode(ts.SyntaxKind.QuestionToken); 45 | } 46 | 47 | return prop; 48 | } 49 | 50 | const safeUnions = ImmutableSet([ 51 | ts.SyntaxKind.TrueKeyword, 52 | ts.SyntaxKind.FalseKeyword, 53 | ts.SyntaxKind.StringLiteral, 54 | ts.SyntaxKind.NumericLiteral, 55 | ts.SyntaxKind.PrefixUnaryExpression, 56 | ts.SyntaxKind.NullKeyword, 57 | ]); 58 | 59 | export function transform(stack: ParsedNode[], options: JsonTsOptions): InterfaceNode[] { 60 | 61 | const wrapper = [{ 62 | kind: ts.SyntaxKind.ObjectLiteralExpression, 63 | _kind: 'ObjectLiteralExpression', 64 | name: options.rootName, 65 | interfaceCandidate: true, 66 | body: stack 67 | }]; 68 | 69 | const interfaces = getInterfaces(wrapper); 70 | 71 | return collapseInterfaces(interfaces); 72 | 73 | function createOne(node: ParsedNode): InterfaceNode { 74 | 75 | const thisMembers = getMembers(node.body); 76 | const item: any = ts.createNode(ts.SyntaxKind.InterfaceDeclaration); 77 | item.name = ts.createIdentifier(newInterfaceName(node)); 78 | item.members = ts.createNodeArray(thisMembers, false); 79 | 80 | return item; 81 | } 82 | 83 | function getInterfaces(nodes: ParsedNode[]): any[] { 84 | return nodes.reduce((acc, node) => { 85 | 86 | if (node.kind === ts.SyntaxKind.ObjectLiteralExpression) { 87 | 88 | const newInterface = createOne(node); 89 | // const asMap = fromJS(newInterface); 90 | 91 | if (node.interfaceCandidate) { 92 | return acc.concat([newInterface], getInterfaces(node.body)); 93 | } 94 | 95 | return acc.concat(getInterfaces(node.body)); 96 | } 97 | 98 | if (node.kind === ts.SyntaxKind.ArrayLiteralExpression) { 99 | 100 | const decorated = node.body.map(arrayNode => { 101 | arrayNode.name = getArrayItemName(node.name); 102 | return arrayNode; 103 | }); 104 | 105 | const other = getInterfaces(decorated); 106 | 107 | return acc.concat(other); 108 | } 109 | 110 | return acc; 111 | 112 | }, []); 113 | } 114 | 115 | function getMembers(stack: ParsedNode[]) { 116 | const members = stack.map(node => { 117 | switch(node.kind) { 118 | case ts.SyntaxKind.FalseKeyword: 119 | case ts.SyntaxKind.TrueKeyword: { 120 | const item = namedProp({name: node.name}); 121 | item.type = ts.createNode(ts.SyntaxKind.BooleanKeyword); 122 | return item; 123 | } 124 | case ts.SyntaxKind.StringLiteral: { 125 | const item = namedProp({name: node.name}); 126 | item.type = ts.createNode(ts.SyntaxKind.StringKeyword); 127 | return item; 128 | } 129 | case ts.SyntaxKind.NullKeyword: { 130 | const item = namedProp({name: node.name}); 131 | item.type = ts.createNode(ts.SyntaxKind.NullKeyword); 132 | return item; 133 | } 134 | case ts.SyntaxKind.NumericLiteral: { 135 | const item = namedProp({name: node.name}); 136 | item.type = ts.createNode(ts.SyntaxKind.NumberKeyword); 137 | return item; 138 | } 139 | case ts.SyntaxKind.ObjectLiteralExpression: { 140 | if (node.interfaceCandidate) { 141 | const item = namedProp({name: node.name}); 142 | item.type = ts.createTypeReferenceNode(newInterfaceName(node), undefined); 143 | return item; 144 | } else { 145 | const item = namedProp({name: node.name}); 146 | item.type = ts.createTypeLiteralNode(getMembers(node.body)); 147 | return item; 148 | } 149 | } 150 | case ts.SyntaxKind.ArrayLiteralExpression: { 151 | if (node.body.length) { 152 | const item = namedProp({name: node.name}); 153 | const elements = getArrayElementsType(node); 154 | item.type = ts.createArrayTypeNode(elements); 155 | return item; 156 | } else { 157 | const item = namedProp({name: node.name}); 158 | const anyNode: any = ts.createNode(ts.SyntaxKind.AnyKeyword); 159 | item.type = ts.createArrayTypeNode(anyNode); 160 | return item; 161 | } 162 | } 163 | } 164 | }); 165 | return members 166 | } 167 | 168 | function getArrayElementsType(node: ParsedNode): any { 169 | const kinds = ImmutableSet(node.body.map(x => x.kind)); 170 | if (kinds.size === 1) { // if there's only 1 kind in the array, it's safe to use type[]; 171 | const kind = kinds.first(); 172 | switch(kind) { 173 | case ts.SyntaxKind.NullKeyword: 174 | case ts.SyntaxKind.StringLiteral: 175 | case ts.SyntaxKind.TrueKeyword: 176 | case ts.SyntaxKind.FalseKeyword: 177 | case ts.SyntaxKind.NumericLiteral: 178 | return ts.createNode(kindMap[kind]); 179 | case ts.SyntaxKind.ObjectLiteralExpression: 180 | const item = ts.createTypeReferenceNode(getArrayInterfaceItemName(node.name), undefined); 181 | return item; 182 | default: return ts.createNode(ts.SyntaxKind.AnyKeyword); 183 | } 184 | } else if (kinds.size === 2) { // a mix of true/false is still a boolean[]; 185 | if (kinds.has(ts.SyntaxKind.TrueKeyword) && kinds.has(ts.SyntaxKind.FalseKeyword)) { 186 | return ts.createNode(ts.SyntaxKind.BooleanKeyword); 187 | } 188 | } 189 | // console.log(node.body); 190 | if (kinds.every(kind => safeUnions.has(kind))) { 191 | 192 | // console.log(node.body); 193 | const types = kinds.map(x => { 194 | return ts.createNode(kindMap[x]); 195 | }).toJS(); 196 | 197 | const item = ts.createNode(ts.SyntaxKind.ParenthesizedType); 198 | (item as any).type = ts.createUnionOrIntersectionTypeNode(ts.SyntaxKind.UnionType, types); 199 | 200 | return item; 201 | } else { 202 | // console.log('Not creating union as this array contains mixed complexr types'); 203 | } 204 | 205 | return ts.createNode(ts.SyntaxKind.AnyKeyword); 206 | } 207 | function newInterfaceName(node: ParsedNode) { 208 | const base = node.name[0].toUpperCase() + node.name.slice(1); 209 | if (options.prefix) { 210 | return options.prefix + base; 211 | } 212 | const qs = needsQuotes(base); 213 | if (qs.needsQuotes) { 214 | return `_` + base; 215 | } 216 | return base; 217 | } 218 | function upper(string) { 219 | return string[0].toUpperCase() + string.slice(1); 220 | } 221 | function pascalCase(input): string { 222 | return startCase(input).replace(/ /g, ''); 223 | } 224 | function getArrayInterfaceItemName(input): string { 225 | if (options.prefix) { 226 | return pascalCase(`${options.prefix}_${input}_Item`); 227 | } 228 | const qs = needsQuotes(input); 229 | if (qs.needsQuotes) { 230 | return '_' + pascalCase(`${input}_Item`); 231 | } 232 | return pascalCase(`${input}_Item`) 233 | } 234 | function getArrayItemName(input) { 235 | return pascalCase(`${input}_Item`) 236 | } 237 | // function getArrayInterfaceItemName(input) { 238 | // return pascalCase(`I_${input}_Item`) 239 | // } 240 | } 241 | 242 | 243 | 244 | -------------------------------------------------------------------------------- /src/util.ts: -------------------------------------------------------------------------------- 1 | import * as ts from "typescript"; 2 | 3 | export function membersMatch(first, second) { 4 | if (first.kind !== second.kind) { 5 | return false; 6 | } 7 | if (first.name.text !== second.name.text) { 8 | return false; 9 | } 10 | if (first.type.kind !== second.type.kind) { 11 | return false; 12 | } 13 | if (first.type.kind === ts.SyntaxKind.ArrayType && second.type.kind === ts.SyntaxKind.ArrayType) { 14 | if (first.type.elementType.kind !== second.type.elementType.kind) { 15 | return false; 16 | } 17 | } 18 | return true; 19 | } 20 | 21 | export function isEmptyArrayType(member) { 22 | if (member.type.kind === ts.SyntaxKind.ArrayType) { 23 | if (member.type.elementType.kind === ts.SyntaxKind.AnyKeyword) { 24 | return true; 25 | } 26 | } 27 | return false; 28 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Basic Options */ 4 | "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'. */ 5 | "module": "commonjs", /* Specify module code generation: 'commonjs', 'amd', 'system', 'umd' or 'es2015'. */ 6 | "lib": ["es2015"], 7 | /* Specify library files to be included in the compilation: */ 8 | // "allowJs": true, /* Allow javascript files to be compiled. */ 9 | // "checkJs": true, /* Report errors in .js files. */ 10 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 11 | "declaration": true, /* Generates corresponding '.d.ts' file. */ 12 | // "sourceMap": true, /* Generates corresponding '.map' file. */ 13 | "outDir": "./dist", /* Redirect output structure to the directory. */ 14 | // "removeComments": true, /* Do not emit comments to output. */ 15 | // "noEmit": true, /* Do not emit outputs. */ 16 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 17 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 18 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 19 | 20 | /* Strict Type-Checking Options */ 21 | // "strict": false, /* Enable all strict type-checking options. */ 22 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 23 | // "strictNullChecks": true, /* Enable strict null checks. */ 24 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 25 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 26 | 27 | /* Additional Checks */ 28 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 29 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 30 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 31 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 32 | 33 | /* Module Resolution Options */ 34 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 35 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 36 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 37 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 38 | // "typeRoots": [], /* List of folders to include type definitions from. */ 39 | // "types": [], /* Type declaration files to be included in compilation. */ 40 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 41 | 42 | /* Source Map Options */ 43 | // "sourceRoot": "./", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 44 | // "mapRoot": "./", /* Specify the location where debugger should locate map files instead of generated locations. */ 45 | "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 46 | "inlineSources": true /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 47 | 48 | /* Experimental Options */ 49 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 50 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 51 | }, 52 | "include": [ 53 | "src/*.ts" 54 | ] 55 | } 56 | --------------------------------------------------------------------------------