1/* global define, require */ 2import { isArray } from '../utils'; 3 4let SourceNode; 5 6try { 7 /* istanbul ignore next */ 8 if (typeof define !== 'function' || !define.amd) { 9 // We don't support this in AMD environments. For these environments, we assume that 10 // they are running on the browser and thus have no need for the source-map library. 11 let SourceMap = require('source-map'); 12 SourceNode = SourceMap.SourceNode; 13 } 14} catch (err) { 15 /* NOP */ 16} 17 18/* istanbul ignore if: tested but not covered in istanbul due to dist build */ 19if (!SourceNode) { 20 SourceNode = function(line, column, srcFile, chunks) { 21 this.src = ''; 22 if (chunks) { 23 this.add(chunks); 24 } 25 }; 26 /* istanbul ignore next */ 27 SourceNode.prototype = { 28 add: function(chunks) { 29 if (isArray(chunks)) { 30 chunks = chunks.join(''); 31 } 32 this.src += chunks; 33 }, 34 prepend: function(chunks) { 35 if (isArray(chunks)) { 36 chunks = chunks.join(''); 37 } 38 this.src = chunks + this.src; 39 }, 40 toStringWithSourceMap: function() { 41 return { code: this.toString() }; 42 }, 43 toString: function() { 44 return this.src; 45 } 46 }; 47} 48 49function castChunk(chunk, codeGen, loc) { 50 if (isArray(chunk)) { 51 let ret = []; 52 53 for (let i = 0, len = chunk.length; i < len; i++) { 54 ret.push(codeGen.wrap(chunk[i], loc)); 55 } 56 return ret; 57 } else if (typeof chunk === 'boolean' || typeof chunk === 'number') { 58 // Handle primitives that the SourceNode will throw up on 59 return chunk + ''; 60 } 61 return chunk; 62} 63 64function CodeGen(srcFile) { 65 this.srcFile = srcFile; 66 this.source = []; 67} 68 69CodeGen.prototype = { 70 isEmpty() { 71 return !this.source.length; 72 }, 73 prepend: function(source, loc) { 74 this.source.unshift(this.wrap(source, loc)); 75 }, 76 push: function(source, loc) { 77 this.source.push(this.wrap(source, loc)); 78 }, 79 80 merge: function() { 81 let source = this.empty(); 82 this.each(function(line) { 83 source.add([' ', line, '\n']); 84 }); 85 return source; 86 }, 87 88 each: function(iter) { 89 for (let i = 0, len = this.source.length; i < len; i++) { 90 iter(this.source[i]); 91 } 92 }, 93 94 empty: function() { 95 let loc = this.currentLocation || { start: {} }; 96 return new SourceNode(loc.start.line, loc.start.column, this.srcFile); 97 }, 98 wrap: function(chunk, loc = this.currentLocation || { start: {} }) { 99 if (chunk instanceof SourceNode) { 100 return chunk; 101 } 102 103 chunk = castChunk(chunk, this, loc); 104 105 return new SourceNode( 106 loc.start.line, 107 loc.start.column, 108 this.srcFile, 109 chunk 110 ); 111 }, 112 113 functionCall: function(fn, type, params) { 114 params = this.generateList(params); 115 return this.wrap([fn, type ? '.' + type + '(' : '(', params, ')']); 116 }, 117 118 quotedString: function(str) { 119 return ( 120 '"' + 121 (str + '') 122 .replace(/\\/g, '\\\\') 123 .replace(/"/g, '\\"') 124 .replace(/\n/g, '\\n') 125 .replace(/\r/g, '\\r') 126 .replace(/\u2028/g, '\\u2028') // Per Ecma-262 7.3 + 7.8.4 127 .replace(/\u2029/g, '\\u2029') + 128 '"' 129 ); 130 }, 131 132 objectLiteral: function(obj) { 133 let pairs = []; 134 135 Object.keys(obj).forEach(key => { 136 let value = castChunk(obj[key], this); 137 if (value !== 'undefined') { 138 pairs.push([this.quotedString(key), ':', value]); 139 } 140 }); 141 142 let ret = this.generateList(pairs); 143 ret.prepend('{'); 144 ret.add('}'); 145 return ret; 146 }, 147 148 generateList: function(entries) { 149 let ret = this.empty(); 150 151 for (let i = 0, len = entries.length; i < len; i++) { 152 if (i) { 153 ret.add(','); 154 } 155 156 ret.add(castChunk(entries[i], this)); 157 } 158 159 return ret; 160 }, 161 162 generateArray: function(entries) { 163 let ret = this.generateList(entries); 164 ret.prepend('['); 165 ret.add(']'); 166 167 return ret; 168 } 169}; 170 171export default CodeGen; 172