├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── example.js ├── lave.js ├── package.json └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | index.js 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # whitelisted branches 2 | branches: 3 | only: 4 | - master 5 | 6 | language: node_js 7 | node_js: 8 | - "node" 9 | - "5" 10 | - "4" 11 | 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Jed Schmidt 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # lave [![Build Status](https://travis-ci.org/jed/lave.svg?branch=master)](https://travis-ci.org/jed/lave) 2 | 3 | lave is [eval][] in reverse; it does for JavaScript what [JSON.stringify][] does for JSON, turning an arbitrary object in memory into the expression, function, or ES6 module needed to create it. 4 | 5 | ## Why not just use JSON.stringify? 6 | 7 | [JSON][] is great data transport, but can only handle a subset of the objects expressible in a JavaScript runtime. This usually results in lossy serializations at best, and `TypeError: Converting circular structure to JSON` at worst. While we can get around such issues by writing JavaScript code to parse this JSON back into the structures we want, now we have to ship that code out of band, which can be a headache. 8 | 9 | Instead of writing a parser for a new language that _can_ represent arbitrary JavaScript runtime values, I built lave to use the best language for the job: JavaScript itself. This allows it to handle the following structures that JSON can't. 10 | 11 | Type | JavaScript | JSON.stringify | lave 12 | ------------------- | --------------------- | -------------------------------------- | ------------------------- 13 | Circular references | `a={}; a.self=a` | :x: TypeError | :white_check_mark: `var a={};a.self=a;a` 14 | Repeated references | `a={}; [a, a]` | :warning: `[{}, {}]` | :white_check_mark: `var a={};[a,a]` 15 | Global object | `global` | :x: TypeError | :white_check_mark: `(0,eval)('this')` [?][global objects] 16 | Built-in objects | `Array.prototype` | :warning: `[]` | :white_check_mark: `Array.prototype` 17 | Boxed primitives | `Object('abc')` | :warning: `"abc"` | :white_check_mark: `Object('abc')` 18 | Functions | `[function(){}]` | :warning: `[null]` | :white_check_mark: `[function(){}]` 19 | Dates | `new Date(1e12)` | :warning: `"2001-09-09T01:46:40.000Z"` | :white_check_mark: `new Date(1000000000000)` 20 | NaN | `NaN` | :warning: `null` | :white_check_mark: `NaN` 21 | Infinity | `Infinity` | :warning: `null` | :white_check_mark: `Infinity` 22 | Sets and Maps | `new Set([1,2,3])` | :warning: `{}` | :white_check_mark: `new Set([1,2,3])` 23 | Sparse arrays | `a=[]; a[2]=0; a` | :warning: `[null,null,0]` | :white_check_mark: `var a=[];a[2]=0;a` 24 | Object properties | `a=[0,1]; a.b=2; a` | :warning: `[0,1]` | :white_check_mark: `var a=[0,1];a.b=2;a` 25 | Custom prototypes | `Object.create(null)` | :warning: `{}` | :white_check_mark: `Object.create(null)` 26 | 27 | Keep in mind that there are some things that not even lave can stringify, such as function closures, or built-in native functions. 28 | 29 | ## Example 30 | 31 | This command... 32 | 33 | ```javascript 34 | node << EOF 35 | var generate = require('escodegen').generate 36 | var lave = require('lave') 37 | 38 | var a = [function(){}, new Date, new Buffer('A'), global] 39 | a.splice(2, 0, a) 40 | 41 | var js = lave(a, {generate, format: 'module'}) 42 | console.log(js) 43 | EOF 44 | ``` 45 | 46 | ...outputs the following JavaScript: 47 | 48 | ```javascript 49 | var a = [ 50 | function (){}, 51 | new Date(1456522677247), 52 | null, 53 | Buffer('QQ==', 'base64'), 54 | (0, eval)('this') 55 | ]; 56 | a[2] = a; 57 | export default a; 58 | ``` 59 | 60 | ## When would I want to use this? 61 | 62 | - **To transport relational data.** Since JSON can only represent hierarchical trees, attempts to serialize a graph require you to define a schema and ship code that reifies the relationship between objects at runtime. For example, if you have list of orders and a list of customers, and each order has a `customerId` property, you need to write and ship code that turns this property into one that references the customer object directly. 63 | 64 | - **To colocate data and dependent logic.** The past few years have seen a long overdue rethink of common best practices for web developers. From markup and logic in [React components][] to styles and markup in [Radium][], we've improved developer productivity by slicing concerns vertically (many concerns per component) instead of horizontally (many components per concern). Having the ability to ship tightly coupled logic and data in one file means fewer moving parts during deployment. 65 | 66 | - **To avoid runtime dependencies.** There are several libraries that give you the ability to serialize and parse graph data, but most of add a parser dependency to your recipients (in the case of JavaScript, usually a browser). Since lave requires only a JavaScript parser, you do not need to incorporate a runtime library to use it. If you prefer the safety of parsing without evaluation and don't mind the dependency, consider using something like [@benjamn][]'s excellent [arson][]. 67 | 68 | ## How does lave work? 69 | 70 | lave uses all of the syntax available in JavaScript to build the most concise representation of an object, such as by preferring literals (`[1,2]`) over assignment (`var a=[];a[0]=1;a[1]=2`). Here's how it works: 71 | 72 | - lave traverses the global object to cache paths for any host object. So if your structure contains `[].slice`, lave knows that you're looking for `Array.prototype.slice`, and uses that path in its place. 73 | 74 | - lave then traverses your object, converting each value that it finds into an abstract syntax graph. It never converts the same object twice; instead it caches all nodes it creates and reuses them any time their corresponding value appears. 75 | 76 | - lave then finds all expressions referenced more than once, and for each one, pulls the expression into a variable declaration, and replaces everywhere that it occurs with its corresponding identifier. This process of removing [dipoles][] converts the abstract syntax graph into a serializable abstract syntax tree. 77 | 78 | - Finally, lave adds any assignment statements needed to fulfil circular references in your original graph, and then returns the expression corresponding to your original root value. 79 | 80 | ## Installation 81 | 82 | Please run the following command to install lave: 83 | 84 | ```bash 85 | $ npm install lave 86 | ``` 87 | 88 | The library has been tested on Node 4.x and 5.x. 89 | 90 | ## API 91 | 92 | ### ast = lave(object, [options]) 93 | 94 | By default, lave takes an `object` and returns an abstract syntax tree (AST) representing the generated JavaScript. Any of the following `options` can also be specified: 95 | 96 | - `generate`: A function that takes an [ESTree][] AST and returns JavaScript code, such as through [escodegen][] or [babel-generator][]. If this is omitted, an AST will be returned, with any functions in the original object serialized using [toString][], and wrapped in an [eval][] call. If this is specified, a JavaScript string will be returned. 97 | - `format`: A string specifying the type of code to output, from the following: 98 | - `expression` (default): Returns code in which the last statement is result expression, such as `var a={};[a, a];`. This is useful when the code is evaluated with [eval][]. 99 | - `function`: Returns the code as a function expression, such as `(function(){var a={};return[a, a]})`. This is useful for inlining as an expression without polluting scope. 100 | - `module`: Returns the code as an ES6 module export, such as `var a={};export default[a, a];`. This is currently useful for integration with a module build process, such as [Rollup][] or [Babel][] transforms. 101 | 102 | ## Addenda 103 | 104 | - Many thanks to [Jamen Marz][] for graciously providing the `lave` name on npm. 105 | - Right before publishing this, I discovered that [uneval][] was a thing. 106 | 107 | [eval]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval 108 | [JSON.stringify]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify 109 | [escodegen]: https://github.com/estools/escodegen 110 | [babel-generator]: https://github.com/babel/babel/tree/master/packages/babel-generator 111 | [ESTree]: https://github.com/estree/estree/blob/master/spec.md 112 | [toString]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/toString 113 | [Jamen Marz]: https://github.com/jamen 114 | [Rollup]: http://rollupjs.org 115 | [Babel]: http://babeljs.io/docs/plugins/transform-es2015-modules-commonjs 116 | [dipoles]: https://en.wikipedia.org/wiki/Dipole_graph 117 | [JSON]: http://json.org/ 118 | [global objects]: http://perfectionkills.com/unnecessarily-comprehensive-look-into-a-rather-insignificant-issue-of-global-objects-creation/ 119 | [React components]: https://facebook.github.io/react/docs/reusable-components.html 120 | [Radium]: https://github.com/FormidableLabs/radium 121 | [@benjamn]: https://github.com/benjamn 122 | [arson]: https://github.com/benjamn/arson 123 | [uneval]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/uneval 124 | -------------------------------------------------------------------------------- /example.js: -------------------------------------------------------------------------------- 1 | var generate = require('escodegen').generate 2 | var lave = require('lave') 3 | 4 | var a = [function(){}, new Date, new Buffer('A'), global] 5 | a.splice(2, 0, a) 6 | 7 | var js = lave(a, {generate, format: 'module'}) 8 | -------------------------------------------------------------------------------- /lave.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | export default function(object, options) { 4 | if (!options) options = {} 5 | 6 | var functions = new Set 7 | var placeholder = Math.random().toString(36).replace(/../, '_') 8 | var functionPattern = RegExp(`${placeholder}(\\d+)`, 'g') 9 | 10 | let cache = new Globals 11 | let statements = [] 12 | 13 | let expression = getExpression(object) 14 | statements.push({type: 'ExpressionStatement', expression}) 15 | 16 | let declarations = getDeclarations(statements) 17 | if (declarations.length) statements.unshift({ 18 | type: 'VariableDeclaration', 19 | declarations, 20 | kind: 'var' 21 | }) 22 | 23 | switch (options.format || 'expression') { 24 | case 'expression': 25 | break 26 | 27 | case 'module': 28 | statements.push({ 29 | type: 'ExportDefaultDeclaration', 30 | declaration: statements.pop().expression 31 | }) 32 | break 33 | 34 | case 'function': 35 | statements.push({ 36 | type: 'ReturnStatement', 37 | argument: statements.pop().expression 38 | }) 39 | 40 | statements = [{ 41 | type: 'ExpressionStatement', 42 | expression: { 43 | type: 'FunctionExpression', 44 | params: [], 45 | body: { 46 | type: 'BlockStatement', 47 | body: statements 48 | } 49 | } 50 | }] 51 | 52 | break 53 | 54 | default: 55 | throw new Error(`Unsupported format: ${options.format}`) 56 | } 57 | 58 | let program = { 59 | type: 'Program', 60 | body: statements 61 | } 62 | 63 | if (!options.generate) return program 64 | 65 | let code = options.generate(program) 66 | return code.replace(functionPattern, (_, i) => { 67 | return Array.from(functions)[i].toString().replace(/^function |^/, 'function ') 68 | }) 69 | 70 | function getExpression(value) { 71 | if (cache.has(value)) return cache.get(value) 72 | 73 | let node = new Object 74 | cache.set(value, node) 75 | 76 | if (Object(value) !== value) { 77 | if (value < 0) { 78 | node.type = 'UnaryExpression' 79 | node.operator = '-' 80 | node.argument = getExpression(Math.abs(value)) 81 | return node 82 | } 83 | 84 | node.value = value 85 | node.type = 'Literal' 86 | return node 87 | } 88 | 89 | let prototype = Object.getPrototypeOf(value) 90 | let propertyNames = Object.getOwnPropertyNames(value) 91 | let properties = new Map(propertyNames.map(name => 92 | [name, Object.getOwnPropertyDescriptor(value, name)] 93 | )) 94 | 95 | switch (prototype) { 96 | case String.prototype: 97 | for (let pair of properties) { 98 | properties.delete(pair[0]) 99 | if (pair[0] == 'length') break 100 | } // fallthrough 101 | 102 | case Number.prototype: 103 | case Boolean.prototype: 104 | node.type = 'CallExpression' 105 | node.callee = getExpression(Object) 106 | node.arguments = [getExpression(value.valueOf())] 107 | break 108 | 109 | case RegExp.prototype: 110 | properties.delete('source') 111 | properties.delete('global') 112 | properties.delete('ignoreCase') 113 | properties.delete('multiline') 114 | properties.delete('lastIndex') 115 | 116 | node.type = 'Literal' 117 | node.value = value 118 | break 119 | 120 | case Date.prototype: 121 | node.type = 'NewExpression' 122 | node.callee = getExpression(Date) 123 | node.arguments = [getExpression(value.valueOf())] 124 | break 125 | 126 | case Buffer.prototype: 127 | for (let pair of properties) { 128 | properties.delete(pair[0]) 129 | if (pair[0] == 'length') break 130 | } 131 | 132 | node.type = 'CallExpression' 133 | node.callee = getExpression(Buffer) 134 | node.arguments = [ 135 | getExpression(value.toString('base64')), 136 | getExpression('base64') 137 | ] 138 | break 139 | 140 | case Error.prototype: 141 | case EvalError.prototype: 142 | case RangeError.prototype: 143 | case ReferenceError.prototype: 144 | case SyntaxError.prototype: 145 | case TypeError.prototype: 146 | case URIError.prototype: 147 | properties.delete('message') 148 | properties.delete('stack') 149 | node.type = 'NewExpression' 150 | node.callee = getExpression(value.constructor) 151 | node.arguments = [getExpression(value.message)] 152 | break 153 | 154 | case Function.prototype: 155 | if (isNativeFunction(value)) { 156 | throw new Error('Native code cannot be serialized.') 157 | } 158 | 159 | properties.delete('length') 160 | properties.delete('name') 161 | properties.delete('arguments') 162 | properties.delete('caller') 163 | if (value.prototype && Object.getOwnPropertyNames(value.prototype).length < 2) { 164 | properties.delete('prototype') 165 | } 166 | 167 | if (options.generate) { 168 | functions.add(value) 169 | let index = Array.from(functions).indexOf(value) 170 | node.type = 'Identifier' 171 | node.name = placeholder + index 172 | break 173 | } 174 | 175 | node.type = 'CallExpression' 176 | node.callee = getExpression(eval) 177 | node.arguments = [getExpression(`(${value.toString()})`)] 178 | break 179 | 180 | case Array.prototype: 181 | node.elements = [] 182 | 183 | let length = properties.get('length').value 184 | let lastIndex = String(length - 1) 185 | if (!length || properties.has(lastIndex)) properties.delete('length') 186 | 187 | for (let property of properties) { 188 | if (property[0] != node.elements.length) break 189 | 190 | let element = getExpression(property[1].value) 191 | if (element.type) { 192 | node.elements.push(element) 193 | properties.delete(property[0]) 194 | } 195 | 196 | else node.elements.push(null) 197 | } 198 | 199 | node.type = 'ArrayExpression' 200 | break 201 | 202 | case Set.prototype: 203 | case WeakSet.prototype: 204 | let members = Array.from(value) 205 | let index = 0 206 | 207 | for (let member of members) { 208 | let element = getExpression(member) 209 | if (element.type) index++ 210 | else break 211 | } 212 | 213 | node.type = 'NewExpression' 214 | node.callee = getExpression(value.constructor) 215 | node.arguments = [] 216 | if (index > 0) { 217 | node.arguments.push(getExpression(members.slice(0, index))) 218 | } 219 | 220 | for (let member of members.slice(index)) { 221 | statements.push({ 222 | type: 'ExpressionStatement', 223 | expression: { 224 | type: 'CallExpression', 225 | callee: { 226 | type: 'MemberExpression', 227 | object: node, 228 | computed: false, 229 | property: {type: 'Identifier', name: 'add'} 230 | }, 231 | arguments: [getExpression(member)] 232 | } 233 | }) 234 | } 235 | break 236 | 237 | case Map.prototype: 238 | case WeakMap.prototype: 239 | let entries = Array.from(value) 240 | 241 | for (let entry of entries) { 242 | let element = getExpression(entry[1]) 243 | if (!element.type) { 244 | entry.pop() 245 | statements.push({ 246 | type: 'ExpressionStatement', 247 | expression: { 248 | type: 'CallExpression', 249 | callee: { 250 | type: 'MemberExpression', 251 | object: node, 252 | computed: false, 253 | property: {type: 'Identifier', name: 'set'} 254 | }, 255 | arguments: [getExpression(entry[0]), element] 256 | } 257 | }) 258 | } 259 | } 260 | 261 | node.arguments = [getExpression(entries)] 262 | node.callee = getExpression(value.constructor) 263 | node.type = 'NewExpression' 264 | break 265 | 266 | case Object.prototype: 267 | node.properties = [] 268 | for (let property of properties) { 269 | let element = getExpression(property[1].value) 270 | 271 | if (!element.type) { 272 | node.properties.push({ 273 | type: 'Property', 274 | key: getExpression(property[0]), 275 | value: getExpression(null) 276 | }) 277 | continue 278 | } 279 | 280 | properties.delete(property[0]) 281 | node.properties.push({ 282 | type: 'Property', 283 | key: getExpression(property[0]), 284 | value: getExpression(property[1].value) 285 | }) 286 | } 287 | 288 | node.type = 'ObjectExpression' 289 | break 290 | 291 | default: 292 | node.type = 'CallExpression' 293 | node.callee = getExpression(Object.create) 294 | node.arguments = [getExpression(prototype)] 295 | break 296 | 297 | } 298 | 299 | for (let property of properties) { 300 | statements.push({ 301 | type: 'ExpressionStatement', 302 | expression: { 303 | type: 'AssignmentExpression', 304 | operator: '=', 305 | left: { 306 | type: 'MemberExpression', 307 | object: node, 308 | computed: !isNaN(property[0]), 309 | property: {type: 310 | 'Identifier', 311 | name: property[0] 312 | } 313 | }, 314 | right: getExpression(property[1].value) 315 | } 316 | }) 317 | } 318 | 319 | return node 320 | } 321 | 322 | function getDeclarations(statements) { 323 | let sites = function crawl(sites, object, key, value) { 324 | if (Object(value) !== value) return 325 | if (value.type == 'Identifier') return 326 | if (value.type == 'Literal') return 327 | 328 | let valueSites = sites.get(value) 329 | if (valueSites) return valueSites.push({object, key}) 330 | 331 | sites.set(value, [{object, key}]) 332 | 333 | for (let property in value) { 334 | crawl(sites, value, property, value[property]) 335 | } 336 | 337 | return sites 338 | }(new Map, {'': statements}, '', statements) 339 | 340 | let declarations = [] 341 | for (let entry of sites) { 342 | if (entry[1].length < 2) continue 343 | 344 | let id = {type: 'Identifier'} 345 | for (let site of entry[1]) site.object[site.key] = id 346 | 347 | declarations.push({ 348 | type: 'VariableDeclarator', 349 | id, 350 | init: entry[0] 351 | }) 352 | } 353 | 354 | declarations.sort((a, b) => has(a.init, b.id) - has(b.init, a.id)) 355 | 356 | let ids = new Identifiers 357 | for (let declaration of declarations) declaration.id.name = ids.next() 358 | 359 | return declarations 360 | } 361 | } 362 | 363 | class Identifiers { 364 | constructor() { this.id = 0 } 365 | next() { 366 | let id = (this.id++).toString(36) 367 | try { Function(`var ${id}`); return id } 368 | catch (e) { return this.next() } 369 | } 370 | } 371 | 372 | function has(parent, child) { 373 | if (parent === child) return true 374 | 375 | if (Object(parent) !== parent) return false 376 | 377 | for (let key in parent) { 378 | if (has(parent[key], child)) return true 379 | } 380 | 381 | return false 382 | } 383 | 384 | let nativeCode = String(Object).match(/{.*/)[0] 385 | function isNativeFunction(fn) { 386 | let source = String(fn) 387 | let index = source.indexOf(nativeCode) 388 | if (index < 0) return false 389 | 390 | let length = index + nativeCode.length 391 | return length === source.length 392 | } 393 | 394 | function Globals() { 395 | let globals = new Map([ 396 | [NaN, {type: 'Identifier', name: 'NaN'}], 397 | [null, {type: 'Literal', value: null}], 398 | [undefined, {type: 'Identifier', name: 'undefined'}], 399 | [Infinity, {type: 'Identifier', name: 'Infinity'}], 400 | [(0,eval)('this'), { 401 | type: 'CallExpression', 402 | callee: { 403 | type: 'SequenceExpression', 404 | expressions: [ 405 | {type: 'Literal', value: 0}, 406 | {type: 'Identifier', name: 'eval'} 407 | ] 408 | }, 409 | "arguments": [{type: 'Literal', value: 'this'}] 410 | }] 411 | ]) 412 | 413 | return crawl(globals, (0, eval)('this')) 414 | } 415 | 416 | const STANDARD_GLOBALS = require('vm') 417 | .runInNewContext('Object.getOwnPropertyNames(this)') 418 | .concat('Buffer') 419 | 420 | function crawl(map, value, object) { 421 | let names = object ? Object.getOwnPropertyNames(value) : STANDARD_GLOBALS 422 | let properties = [] 423 | 424 | for (let name of names) { 425 | let descriptor = Object.getOwnPropertyDescriptor(value, name) 426 | 427 | if (Object(descriptor.value) !== descriptor.value) continue 428 | if (map.has(descriptor.value)) continue 429 | 430 | let property = {type: 'Identifier', name} 431 | if (object) property = {type: 'MemberExpression', object, property} 432 | 433 | map.set(descriptor.value, property) 434 | 435 | properties.push({value: descriptor.value, object: property}) 436 | } 437 | 438 | for (let property of properties) { 439 | crawl(map, property.value, property.object) 440 | } 441 | 442 | return map 443 | } 444 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lave", 3 | "version": "1.1.9", 4 | "description": "eval in reverse: stringifying all the stuff that JSON.stringify won't", 5 | "repository": "jed/lave", 6 | "main": "index.js", 7 | "jsnext:main": "lave.js", 8 | "devDependencies": { 9 | "escodegen": "^1.8.0", 10 | "rollup": "^0.25.4" 11 | }, 12 | "scripts": { 13 | "build": "rollup lave.js -f cjs -o index.js", 14 | "prepublish": "npm run build", 15 | "test": "npm run build && rollup test.js -e escodegen,assert,. -f cjs | node" 16 | }, 17 | "engines": { 18 | "node": ">= 4" 19 | }, 20 | "keywords": [ 21 | "stringify", 22 | "uneval", 23 | "circular", 24 | "json", 25 | "serialize" 26 | ], 27 | "tonicExampleFilename": "example.js", 28 | "publishConfig": { 29 | "registry": "https://npm.pkg.github.com/@jed" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | import {equal} from 'assert' 4 | import escodegen from 'escodegen' 5 | import lave from '.' 6 | 7 | const tests = { 8 | number: [ 123 , `123` ], 9 | negative: [ -123 , `-123` ], 10 | string: [ 'abc' , `'abc'` ], 11 | boolean: [ true , `true` ], 12 | Number: [ new Number(123) , `Object(123)` ], 13 | String: [ new String('abc') , `Object('abc')` ], 14 | Boolean: [ new Boolean(true) , `Object(true)` ], 15 | undefined: [ void 0 , `undefined` ], 16 | null: [ null , `null` ], 17 | NaN: [ NaN , `NaN` ], 18 | Infinity: [ Infinity , `Infinity` ], 19 | RegExp: [ /regexp/img , `/regexp/gim` ], 20 | Buffer: [ new Buffer('A') , `Buffer('QQ==','base64')` ], 21 | Date: [ new Date(1e12) , `new Date(1000000000000)` ], 22 | Function: [ [function (o){o}] , `[function (o){o}]` ], 23 | Error: [ new Error('XXX') , `new Error('XXX')` ], 24 | Array: [ [1,2,3] , `[1,2,3]` ], 25 | sparse: [ Array(10) , `var a=[];a.length=10;a` ], 26 | Set: [ new Set([1,2,3]) , `new Set([1,2,3])` ], 27 | Map: [ new Map([[1,2]]) , `new Map([[1,2]])` ], 28 | cycleMap: [ (a=>a.set(0,a))(new Map) , `var a=new Map([[0]]);a.set(0,a);a` ], 29 | cycleSet: [ (a=>a.add(a).add(0))(new Set) , `var a=new Set();a.add(a);a.add(0);a` ], 30 | global: [ root , `(0,eval)('this')` ], 31 | slice: [ [].slice , `Array.prototype.slice` ], 32 | arrcycle: [ (a=>a[0]=a)([]) , `var a=[,];a[0]=a;a` ], 33 | objcycle: [ (a=>a.a=a)({}) , `var a={'a':null};a.a=a;a` ], 34 | dipole: [ (a=>[a,a])({}) , `var a={};[a,a]` ], 35 | property: [ Object.assign([], {a:0}) , `var a=[];a.a=0;a` ], 36 | prototype: [ Object.create(null) , `Object.create(null)` ] 37 | } 38 | 39 | const format = {compact: true, semicolons: false} 40 | const options = {generate: ast => escodegen.generate(ast, {format})} 41 | 42 | for (let name in tests) { 43 | let expected = tests[name][1] 44 | let actual = lave(tests[name][0], options) 45 | 46 | equal(actual, expected, ` 47 | expected ${name}: ${expected} 48 | actual ${name}: ${actual} 49 | `) 50 | } 51 | 52 | options.format = 'module' 53 | equal(lave(1, options), 'export default 1') 54 | 55 | options.format = 'function' 56 | equal(lave(1, options), '(function(){return 1})') 57 | --------------------------------------------------------------------------------