Pinsave/counterstrike
1
1/* -*- Mode: js; js-indent-level: 2; -*- */2/*3 * Copyright 2011 Mozilla Foundation and contributors4 * Licensed under the New BSD license. See LICENSE or:5 * http://opensource.org/licenses/BSD-3-Clause6 */7 8var SourceMapGenerator = require('./source-map-generator').SourceMapGenerator;9var util = require('./util');10 11// Matches a Windows-style `\r\n` newline or a `\n` newline used by all other12// operating systems these days (capturing the result).13var REGEX_NEWLINE = /(\r?\n)/;14 15// Newline character code for charCodeAt() comparisons16var NEWLINE_CODE = 10;17 18// Private symbol for identifying `SourceNode`s when multiple versions of19// the source-map library are loaded. This MUST NOT CHANGE across20// versions!21var isSourceNode = "$$$isSourceNode$$$";22 23/**24 * SourceNodes provide a way to abstract over interpolating/concatenating25 * snippets of generated JavaScript source code while maintaining the line and26 * column information associated with the original source code.27 *28 * @param aLine The original line number.29 * @param aColumn The original column number.30 * @param aSource The original source's filename.31 * @param aChunks Optional. An array of strings which are snippets of32 * generated JS, or other SourceNodes.33 * @param aName The original identifier.34 */35function SourceNode(aLine, aColumn, aSource, aChunks, aName) {36 this.children = [];37 this.sourceContents = {};38 this.line = aLine == null ? null : aLine;39 this.column = aColumn == null ? null : aColumn;40 this.source = aSource == null ? null : aSource;41 this.name = aName == null ? null : aName;42 this[isSourceNode] = true;43 if (aChunks != null) this.add(aChunks);44}45 46/**47 * Creates a SourceNode from generated code and a SourceMapConsumer.48 *49 * @param aGeneratedCode The generated code50 * @param aSourceMapConsumer The SourceMap for the generated code51 * @param aRelativePath Optional. The path that relative sources in the52 * SourceMapConsumer should be relative to.53 */54SourceNode.fromStringWithSourceMap =55 function SourceNode_fromStringWithSourceMap(aGeneratedCode, aSourceMapConsumer, aRelativePath) {56 // The SourceNode we want to fill with the generated code57 // and the SourceMap58 var node = new SourceNode();59 60 // All even indices of this array are one line of the generated code,61 // while all odd indices are the newlines between two adjacent lines62 // (since `REGEX_NEWLINE` captures its match).63 // Processed fragments are accessed by calling `shiftNextLine`.64 var remainingLines = aGeneratedCode.split(REGEX_NEWLINE);65 var remainingLinesIndex = 0;66 var shiftNextLine = function() {67 var lineContents = getNextLine();68 // The last line of a file might not have a newline.69 var newLine = getNextLine() || "";70 return lineContents + newLine;71 72 function getNextLine() {73 return remainingLinesIndex < remainingLines.length ?74 remainingLines[remainingLinesIndex++] : undefined;75 }76 };77 78 // We need to remember the position of "remainingLines"79 var lastGeneratedLine = 1, lastGeneratedColumn = 0;80 81 // The generate SourceNodes we need a code range.82 // To extract it current and last mapping is used.83 // Here we store the last mapping.84 var lastMapping = null;85 86 aSourceMapConsumer.eachMapping(function (mapping) {87 if (lastMapping !== null) {88 // We add the code from "lastMapping" to "mapping":89 // First check if there is a new line in between.90 if (lastGeneratedLine < mapping.generatedLine) {91 // Associate first line with "lastMapping"92 addMappingWithCode(lastMapping, shiftNextLine());93 lastGeneratedLine++;94 lastGeneratedColumn = 0;95 // The remaining code is added without mapping96 } else {97 // There is no new line in between.98 // Associate the code between "lastGeneratedColumn" and99 // "mapping.generatedColumn" with "lastMapping"100 var nextLine = remainingLines[remainingLinesIndex] || '';101 var code = nextLine.substr(0, mapping.generatedColumn -102 lastGeneratedColumn);103 remainingLines[remainingLinesIndex] = nextLine.substr(mapping.generatedColumn -104 lastGeneratedColumn);105 lastGeneratedColumn = mapping.generatedColumn;106 addMappingWithCode(lastMapping, code);107 // No more remaining code, continue108 lastMapping = mapping;109 return;110 }111 }112 // We add the generated code until the first mapping113 // to the SourceNode without any mapping.114 // Each line is added as separate string.115 while (lastGeneratedLine < mapping.generatedLine) {116 node.add(shiftNextLine());117 lastGeneratedLine++;118 }119 if (lastGeneratedColumn < mapping.generatedColumn) {120 var nextLine = remainingLines[remainingLinesIndex] || '';121 node.add(nextLine.substr(0, mapping.generatedColumn));122 remainingLines[remainingLinesIndex] = nextLine.substr(mapping.generatedColumn);123 lastGeneratedColumn = mapping.generatedColumn;124 }125 lastMapping = mapping;126 }, this);127 // We have processed all mappings.128 if (remainingLinesIndex < remainingLines.length) {129 if (lastMapping) {130 // Associate the remaining code in the current line with "lastMapping"131 addMappingWithCode(lastMapping, shiftNextLine());132 }133 // and add the remaining lines without any mapping134 node.add(remainingLines.splice(remainingLinesIndex).join(""));135 }136 137 // Copy sourcesContent into SourceNode138 aSourceMapConsumer.sources.forEach(function (sourceFile) {139 var content = aSourceMapConsumer.sourceContentFor(sourceFile);140 if (content != null) {141 if (aRelativePath != null) {142 sourceFile = util.join(aRelativePath, sourceFile);143 }144 node.setSourceContent(sourceFile, content);145 }146 });147 148 return node;149 150 function addMappingWithCode(mapping, code) {151 if (mapping === null || mapping.source === undefined) {152 node.add(code);153 } else {154 var source = aRelativePath155 ? util.join(aRelativePath, mapping.source)156 : mapping.source;157 node.add(new SourceNode(mapping.originalLine,158 mapping.originalColumn,159 source,160 code,161 mapping.name));162 }163 }164 };165 166/**167 * Add a chunk of generated JS to this source node.168 *169 * @param aChunk A string snippet of generated JS code, another instance of170 * SourceNode, or an array where each member is one of those things.171 */172SourceNode.prototype.add = function SourceNode_add(aChunk) {173 if (Array.isArray(aChunk)) {174 aChunk.forEach(function (chunk) {175 this.add(chunk);176 }, this);177 }178 else if (aChunk[isSourceNode] || typeof aChunk === "string") {179 if (aChunk) {180 this.children.push(aChunk);181 }182 }183 else {184 throw new TypeError(185 "Expected a SourceNode, string, or an array of SourceNodes and strings. Got " + aChunk186 );187 }188 return this;189};190 191/**192 * Add a chunk of generated JS to the beginning of this source node.193 *194 * @param aChunk A string snippet of generated JS code, another instance of195 * SourceNode, or an array where each member is one of those things.196 */197SourceNode.prototype.prepend = function SourceNode_prepend(aChunk) {198 if (Array.isArray(aChunk)) {199 for (var i = aChunk.length-1; i >= 0; i--) {200 this.prepend(aChunk[i]);201 }202 }203 else if (aChunk[isSourceNode] || typeof aChunk === "string") {204 this.children.unshift(aChunk);205 }206 else {207 throw new TypeError(208 "Expected a SourceNode, string, or an array of SourceNodes and strings. Got " + aChunk209 );210 }211 return this;212};213 214/**215 * Walk over the tree of JS snippets in this node and its children. The216 * walking function is called once for each snippet of JS and is passed that217 * snippet and the its original associated source's line/column location.218 *219 * @param aFn The traversal function.220 */221SourceNode.prototype.walk = function SourceNode_walk(aFn) {222 var chunk;223 for (var i = 0, len = this.children.length; i < len; i++) {224 chunk = this.children[i];225 if (chunk[isSourceNode]) {226 chunk.walk(aFn);227 }228 else {229 if (chunk !== '') {230 aFn(chunk, { source: this.source,231 line: this.line,232 column: this.column,233 name: this.name });234 }235 }236 }237};238 239/**240 * Like `String.prototype.join` except for SourceNodes. Inserts `aStr` between241 * each of `this.children`.242 *243 * @param aSep The separator.244 */245SourceNode.prototype.join = function SourceNode_join(aSep) {246 var newChildren;247 var i;248 var len = this.children.length;249 if (len > 0) {250 newChildren = [];251 for (i = 0; i < len-1; i++) {252 newChildren.push(this.children[i]);253 newChildren.push(aSep);254 }255 newChildren.push(this.children[i]);256 this.children = newChildren;257 }258 return this;259};260 261/**262 * Call String.prototype.replace on the very right-most source snippet. Useful263 * for trimming whitespace from the end of a source node, etc.264 *265 * @param aPattern The pattern to replace.266 * @param aReplacement The thing to replace the pattern with.267 */268SourceNode.prototype.replaceRight = function SourceNode_replaceRight(aPattern, aReplacement) {269 var lastChild = this.children[this.children.length - 1];270 if (lastChild[isSourceNode]) {271 lastChild.replaceRight(aPattern, aReplacement);272 }273 else if (typeof lastChild === 'string') {274 this.children[this.children.length - 1] = lastChild.replace(aPattern, aReplacement);275 }276 else {277 this.children.push(''.replace(aPattern, aReplacement));278 }279 return this;280};281 282/**283 * Set the source content for a source file. This will be added to the SourceMapGenerator284 * in the sourcesContent field.285 *286 * @param aSourceFile The filename of the source file287 * @param aSourceContent The content of the source file288 */289SourceNode.prototype.setSourceContent =290 function SourceNode_setSourceContent(aSourceFile, aSourceContent) {291 this.sourceContents[util.toSetString(aSourceFile)] = aSourceContent;292 };293 294/**295 * Walk over the tree of SourceNodes. The walking function is called for each296 * source file content and is passed the filename and source content.297 *298 * @param aFn The traversal function.299 */300SourceNode.prototype.walkSourceContents =301 function SourceNode_walkSourceContents(aFn) {302 for (var i = 0, len = this.children.length; i < len; i++) {303 if (this.children[i][isSourceNode]) {304 this.children[i].walkSourceContents(aFn);305 }306 }307 308 var sources = Object.keys(this.sourceContents);309 for (var i = 0, len = sources.length; i < len; i++) {310 aFn(util.fromSetString(sources[i]), this.sourceContents[sources[i]]);311 }312 };313 314/**315 * Return the string representation of this source node. Walks over the tree316 * and concatenates all the various snippets together to one string.317 */318SourceNode.prototype.toString = function SourceNode_toString() {319 var str = "";320 this.walk(function (chunk) {321 str += chunk;322 });323 return str;324};325 326/**327 * Returns the string representation of this source node along with a source328 * map.329 */330SourceNode.prototype.toStringWithSourceMap = function SourceNode_toStringWithSourceMap(aArgs) {331 var generated = {332 code: "",333 line: 1,334 column: 0335 };336 var map = new SourceMapGenerator(aArgs);337 var sourceMappingActive = false;338 var lastOriginalSource = null;339 var lastOriginalLine = null;340 var lastOriginalColumn = null;341 var lastOriginalName = null;342 this.walk(function (chunk, original) {343 generated.code += chunk;344 if (original.source !== null345 && original.line !== null346 && original.column !== null) {347 if(lastOriginalSource !== original.source348 || lastOriginalLine !== original.line349 || lastOriginalColumn !== original.column350 || lastOriginalName !== original.name) {351 map.addMapping({352 source: original.source,353 original: {354 line: original.line,355 column: original.column356 },357 generated: {358 line: generated.line,359 column: generated.column360 },361 name: original.name362 });363 }364 lastOriginalSource = original.source;365 lastOriginalLine = original.line;366 lastOriginalColumn = original.column;367 lastOriginalName = original.name;368 sourceMappingActive = true;369 } else if (sourceMappingActive) {370 map.addMapping({371 generated: {372 line: generated.line,373 column: generated.column374 }375 });376 lastOriginalSource = null;377 sourceMappingActive = false;378 }379 for (var idx = 0, length = chunk.length; idx < length; idx++) {380 if (chunk.charCodeAt(idx) === NEWLINE_CODE) {381 generated.line++;382 generated.column = 0;383 // Mappings end at eol384 if (idx + 1 === length) {385 lastOriginalSource = null;386 sourceMappingActive = false;387 } else if (sourceMappingActive) {388 map.addMapping({389 source: original.source,390 original: {391 line: original.line,392 column: original.column393 },394 generated: {395 line: generated.line,396 column: generated.column397 },398 name: original.name399 });400 }401 } else {402 generated.column++;403 }404 }405 });406 this.walkSourceContents(function (sourceFile, sourceContent) {407 map.setSourceContent(sourceFile, sourceContent);408 });409 410 return { code: generated.code, map: map };411};412 413exports.SourceNode = SourceNode;414 