1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
|
#! /usr/bin/env node
"use strict";
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('// EMSCRIPTEN_START_FUNCS');
var functionEndIdx = source.lastIndexOf('// EMSCRIPTEN_END_FUNCS');
var lineCount = countLines(source.slice(0, functionStartIdx));
for (var i = functionStartIdx; i < functionEndIdx; i++) {
var c = source[i];
var nextC = source[i+1];
if (c === '\n') lineCount++;
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;
}
}
}
function generateMap(fileName, sourceRoot, mapFileBaseName) {
var fs = require('fs');
var path = require('path');
var SourceMapGenerator = require('source-map').SourceMapGenerator;
var generator = new SourceMapGenerator({ file: fileName });
var generatedSource = fs.readFileSync(fileName, 'utf-8');
var seenFiles = Object.create(null);
extractComments(generatedSource, function(content, generatedLineNumber) {
var matches = /@line (\d+) "([^"]*)"/.exec(content);
if (matches === null) return;
var originalLineNumber = parseInt(matches[1], 10);
var originalFileName = matches[2];
if (!(originalFileName in seenFiles)) {
seenFiles[originalFileName] = true;
try {
generator.setSourceContent(originalFileName,
fs.readFileSync(sourceRoot + "/" + originalFileName));
} catch (e) {
console.warn("Unable to find original file for " + originalFileName);
}
}
generator.addMapping({
generated: { line: generatedLineNumber, column: 0 },
original: { line: originalLineNumber, column: 0 },
source: originalFileName
});
});
var mapFileName = mapFileBaseName + '.map';
fs.writeFileSync(mapFileName, generator.toString());
var lastLine = generatedSource.slice(generatedSource.lastIndexOf('\n'));
if (!/sourceMappingURL/.test(lastLine))
fs.appendFileSync(fileName, '//@ sourceMappingURL=' + path.basename(mapFileName));
}
if (require.main === module) {
if (process.argv.length < 3) {
console.log('Usage: ./sourcemapper.js <filename> <source root (default: .)> ' +
'<map file basename (default: filename)>');
process.exit(1);
} else {
var sourceRoot = process.argv.length > 3 ? process.argv[3] : ".";
var mapFileBaseName = process.argv.length > 4 ? process.argv[4] : process.argv[2];
generateMap(process.argv[2], sourceRoot, mapFileBaseName);
}
}
|