aboutsummaryrefslogtreecommitdiff
path: root/tools/source-maps
diff options
context:
space:
mode:
Diffstat (limited to 'tools/source-maps')
-rw-r--r--tools/source-maps/sourcemap2json.js15
-rwxr-xr-xtools/source-maps/sourcemapper.js177
2 files changed, 192 insertions, 0 deletions
diff --git a/tools/source-maps/sourcemap2json.js b/tools/source-maps/sourcemap2json.js
new file mode 100644
index 00000000..5dd162b2
--- /dev/null
+++ b/tools/source-maps/sourcemap2json.js
@@ -0,0 +1,15 @@
+/*
+ * Quick utility script for the Python test script to call. Could be replaced if
+ * a good Python source map library is found.
+ */
+var SourceMapConsumer = require('source-map').SourceMapConsumer;
+var fs = require('fs');
+
+var consumer = new SourceMapConsumer(fs.readFileSync(process.argv[2], 'utf-8'));
+var mappings = [];
+
+consumer.eachMapping(function(mapping) {
+ mappings.push(mapping);
+});
+
+console.log(JSON.stringify(mappings));
diff --git a/tools/source-maps/sourcemapper.js b/tools/source-maps/sourcemapper.js
new file mode 100755
index 00000000..fa908900
--- /dev/null
+++ b/tools/source-maps/sourcemapper.js
@@ -0,0 +1,177 @@
+#! /usr/bin/env node
+
+"use strict";
+
+var fs = require('fs');
+var path = require('path');
+
+var START_MARKER = '// EMSCRIPTEN_START_FUNCS\n';
+var END_MARKER = '// EMSCRIPTEN_END_FUNCS\n';
+
+function countLines(s) {
+ var count = 0;
+ for (var i = 0, l = s.length; i < l; i ++) {
+ if (s[i] === '\n') count++;
+ }
+ return count;
+}
+
+/*
+ * Extracts the line (not block) comments from the generated function code and
+ * invokes commentHandler with (comment content, line number of comment). This
+ * function implements a simplistic lexer with the following assumptions:
+ * 1. "// EMSCRIPTEN_START_FUNCS" and "// EMSCRIPTEN_END_FUNCS" are unique
+ * markers for separating library code from generated function code. Things
+ * will break if they appear as part of a string in library code (but OK if
+ * they occur in generated code).
+ * 2. Between these two markers, no regexes are used.
+ */
+function extractComments(source, commentHandler) {
+ var state = 'code';
+ var commentContent = '';
+ var functionStartIdx = source.indexOf(START_MARKER);
+ var functionEndIdx = source.lastIndexOf(END_MARKER);
+ var lineCount = countLines(source.slice(0, functionStartIdx)) + 2;
+
+ for (var i = functionStartIdx + START_MARKER.length; i < functionEndIdx; i++) {
+ var c = source[i];
+ var nextC = source[i+1];
+ switch (state) {
+ case 'code':
+ if (c === '/') {
+ if (nextC === '/') { state = 'lineComment'; i++; }
+ else if (nextC === '*') { state = 'blockComment'; i++; }
+ }
+ else if (c === '"') state = 'doubleQuotedString';
+ else if (c === '\'') state = 'singleQuotedString';
+ break;
+ case 'lineComment':
+ if (c === '\n') {
+ state = 'code';
+ commentHandler(commentContent, lineCount);
+ commentContent = "";
+ } else {
+ commentContent += c;
+ }
+ break;
+ case 'blockComment':
+ if (c === '*' && nextC === '/') state = 'code';
+ case 'singleQuotedString':
+ if (c === '\\') i++;
+ else if (c === '\'') state = 'code';
+ break;
+ case 'doubleQuotedString':
+ if (c === '\\') i++;
+ else if (c === '"') state = 'code';
+ break;
+ }
+
+ if (c === '\n') lineCount++;
+ }
+}
+
+function getMappings(source) {
+ // generatedLineNumber -> { originalLineNumber, originalFileName }
+ var mappings = {};
+ extractComments(source, function(content, generatedLineNumber) {
+ var matches = /@line (\d+)(?: "([^"]*)")?/.exec(content);
+ if (matches === null) return;
+ var originalFileName = matches[2];
+ mappings[generatedLineNumber] = {
+ originalLineNumber: parseInt(matches[1], 10),
+ originalFileName: originalFileName
+ }
+ });
+ return mappings;
+}
+
+function generateMap(mappings, sourceRoot, mapFileBaseName, generatedLineOffset) {
+ var SourceMapGenerator = require('source-map').SourceMapGenerator;
+
+ var generator = new SourceMapGenerator({ file: mapFileBaseName });
+ var seenFiles = Object.create(null);
+
+ for (var generatedLineNumber in mappings) {
+ var generatedLineNumber = parseInt(generatedLineNumber, 10);
+ var mapping = mappings[generatedLineNumber];
+ var originalFileName = mapping.originalFileName;
+ generator.addMapping({
+ generated: { line: generatedLineNumber + generatedLineOffset, column: 0 },
+ original: { line: mapping.originalLineNumber, column: 0 },
+ source: originalFileName
+ });
+
+ // we could call setSourceContent repeatedly, but readFileSync is slow, so
+ // avoid doing it unnecessarily
+ if (!(originalFileName in seenFiles)) {
+ seenFiles[originalFileName] = true;
+ var rootedPath = originalFileName[0] === path.sep ?
+ originalFileName : path.join(sourceRoot, originalFileName);
+ try {
+ generator.setSourceContent(originalFileName, fs.readFileSync(rootedPath, 'utf-8'));
+ } catch (e) {
+ console.warn("sourcemapper: Unable to find original file for " + originalFileName +
+ " at " + rootedPath);
+ }
+ }
+ }
+
+ fs.writeFileSync(mapFileBaseName + '.map', generator.toString());
+}
+
+function appendMappingURL(fileName, source, mapFileName) {
+ var lastLine = source.slice(source.lastIndexOf('\n'));
+ if (!/sourceMappingURL/.test(lastLine))
+ fs.appendFileSync(fileName, '//@ sourceMappingURL=' + path.basename(mapFileName));
+}
+
+function parseArgs(args) {
+ var rv = { _: [] }; // unflagged args go into `_`; similar to the optimist library
+ for (var i = 0; i < args.length; i++) {
+ if (/^--/.test(args[i])) rv[args[i].slice(2)] = args[++i];
+ else rv._.push(args[i]);
+ }
+ return rv;
+}
+
+if (require.main === module) {
+ if (process.argv.length < 3) {
+ console.log('Usage: ./sourcemapper.js <original js> <optimized js file ...> \\\n' +
+ '\t--sourceRoot <default "."> \\\n' +
+ '\t--mapFileBaseName <default `filename`> \\\n' +
+ '\t--offset <default 0>');
+ process.exit(1);
+ } else {
+ var opts = parseArgs(process.argv.slice(2));
+ var fileName = opts._[0];
+ var sourceRoot = opts.sourceRoot ? opts.sourceRoot : ".";
+ var mapFileBaseName = opts.mapFileBaseName ? opts.mapFileBaseName : fileName;
+ var generatedLineOffset = opts.offset ? parseInt(opts.offset, 10) : 0;
+
+ var generatedSource = fs.readFileSync(fileName, 'utf-8');
+ var source = generatedSource;
+ var mappings = getMappings(generatedSource);
+ for (var i = 1, l = opts._.length; i < l; i ++) {
+ var optimizedSource = fs.readFileSync(opts._[i], 'utf-8')
+ var optimizedMappings = getMappings(optimizedSource);
+ var newMappings = {};
+ // uglify processes the code between EMSCRIPTEN_START_FUNCS and
+ // EMSCRIPTEN_END_FUNCS, so its line number maps are relative to those
+ // markers. we correct for that here. +2 = 1 for the newline in the marker
+ // and 1 to make it a 1-based index.
+ var startFuncsLineNumber = countLines(source.slice(0, source.indexOf(START_MARKER))) + 2;
+ for (var line in optimizedMappings) {
+ var originalLineNumber = optimizedMappings[line].originalLineNumber + startFuncsLineNumber;
+ if (originalLineNumber in mappings) {
+ newMappings[line] = mappings[originalLineNumber];
+ }
+ }
+ mappings = newMappings;
+ source = optimizedSource;
+ }
+
+ generateMap(mappings, sourceRoot, mapFileBaseName, generatedLineOffset);
+ appendMappingURL(opts._[opts._.length - 1], generatedSource,
+ opts.mapFileBaseName + '.map');
+ }
+}