1// workaround for tty output truncation upon process.exit()
2[process.stdout, process.stderr].forEach(function(stream){
3    if (stream._handle && stream._handle.setBlocking)
4        stream._handle.setBlocking(true);
5});
6
7var path = require("path");
8var fs = require("fs");
9
10var UglifyJS = exports;
11var FILES = UglifyJS.FILES = [
12    "../lib/utils.js",
13    "../lib/ast.js",
14    "../lib/parse.js",
15    "../lib/transform.js",
16    "../lib/scope.js",
17    "../lib/output.js",
18    "../lib/compress.js",
19    "../lib/sourcemap.js",
20    "../lib/mozilla-ast.js",
21    "../lib/propmangle.js",
22    "./exports.js",
23].map(function(file){
24    return require.resolve(file);
25});
26
27new Function("MOZ_SourceMap", "exports", FILES.map(function(file){
28    return fs.readFileSync(file, "utf8");
29}).join("\n\n"))(
30    require("source-map"),
31    UglifyJS
32);
33
34UglifyJS.AST_Node.warn_function = function(txt) {
35    console.error("WARN: %s", txt);
36};
37
38function read_source_map(code) {
39    var match = /\n\/\/# sourceMappingURL=data:application\/json(;.*?)?;base64,(.*)/.exec(code);
40    if (!match) {
41        UglifyJS.AST_Node.warn("inline source map not found");
42        return null;
43    }
44    return JSON.parse(new Buffer(match[2], "base64"));
45}
46
47UglifyJS.minify = function(files, options) {
48    options = UglifyJS.defaults(options, {
49        compress         : {},
50        fromString       : false,
51        inSourceMap      : null,
52        mangle           : {},
53        mangleProperties : false,
54        nameCache        : null,
55        outFileName      : null,
56        output           : null,
57        outSourceMap     : null,
58        parse            : {},
59        sourceMapInline  : false,
60        sourceMapUrl     : null,
61        sourceRoot       : null,
62        spidermonkey     : false,
63        warnings         : false,
64    });
65    UglifyJS.base54.reset();
66
67    var inMap = options.inSourceMap;
68    if (typeof inMap == "string" && inMap != "inline") {
69        inMap = JSON.parse(fs.readFileSync(inMap, "utf8"));
70    }
71
72    // 1. parse
73    var toplevel = null,
74        sourcesContent = {};
75
76    if (options.spidermonkey) {
77        if (inMap == "inline") {
78            throw new Error("inline source map only works with built-in parser");
79        }
80        toplevel = UglifyJS.AST_Node.from_mozilla_ast(files);
81    } else {
82        function addFile(file, fileUrl) {
83            var code = options.fromString
84                ? file
85                : fs.readFileSync(file, "utf8");
86            if (inMap == "inline") {
87                inMap = read_source_map(code);
88            }
89            sourcesContent[fileUrl] = code;
90            toplevel = UglifyJS.parse(code, {
91                filename: fileUrl,
92                toplevel: toplevel,
93                bare_returns: options.parse ? options.parse.bare_returns : undefined
94            });
95        }
96        if (!options.fromString) {
97            files = UglifyJS.simple_glob(files);
98            if (inMap == "inline" && files.length > 1) {
99                throw new Error("inline source map only works with singular input");
100            }
101        }
102        [].concat(files).forEach(function (files, i) {
103            if (typeof files === 'string') {
104                addFile(files, options.fromString ? i : files);
105            } else {
106                for (var fileUrl in files) {
107                    addFile(files[fileUrl], fileUrl);
108                }
109            }
110        });
111    }
112    if (options.wrap) {
113      toplevel = toplevel.wrap_commonjs(options.wrap, options.exportAll);
114    }
115
116    // 2. compress
117    if (options.compress) {
118        var compress = { warnings: options.warnings };
119        UglifyJS.merge(compress, options.compress);
120        toplevel.figure_out_scope(options.mangle);
121        var sq = UglifyJS.Compressor(compress);
122        toplevel = sq.compress(toplevel);
123    }
124
125    // 3. mangle properties
126    if (options.mangleProperties || options.nameCache) {
127        options.mangleProperties.cache = UglifyJS.readNameCache(options.nameCache, "props");
128        toplevel = UglifyJS.mangle_properties(toplevel, options.mangleProperties);
129        UglifyJS.writeNameCache(options.nameCache, "props", options.mangleProperties.cache);
130    }
131
132    // 4. mangle
133    if (options.mangle) {
134        toplevel.figure_out_scope(options.mangle);
135        toplevel.compute_char_frequency(options.mangle);
136        toplevel.mangle_names(options.mangle);
137    }
138
139    // 5. output
140    var output = { max_line_len: 32000 };
141    if (options.outSourceMap || options.sourceMapInline) {
142        output.source_map = UglifyJS.SourceMap({
143            // prefer outFileName, otherwise use outSourceMap without .map suffix
144            file: options.outFileName || (typeof options.outSourceMap === 'string' ? options.outSourceMap.replace(/\.map$/i, '') : null),
145            orig: inMap,
146            root: options.sourceRoot
147        });
148        if (options.sourceMapIncludeSources) {
149            for (var file in sourcesContent) {
150                if (sourcesContent.hasOwnProperty(file)) {
151                    output.source_map.get().setSourceContent(file, sourcesContent[file]);
152                }
153            }
154        }
155
156    }
157    if (options.output) {
158        UglifyJS.merge(output, options.output);
159    }
160    var stream = UglifyJS.OutputStream(output);
161    toplevel.print(stream);
162
163
164    var source_map = output.source_map;
165    if (source_map) {
166        source_map = source_map + "";
167    }
168
169    var mappingUrlPrefix = "\n//# sourceMappingURL=";
170    if (options.sourceMapInline) {
171        stream += mappingUrlPrefix + "data:application/json;charset=utf-8;base64," + new Buffer(source_map).toString("base64");
172    } else if (options.outSourceMap && typeof options.outSourceMap === "string" && options.sourceMapUrl !== false) {
173        stream += mappingUrlPrefix + (typeof options.sourceMapUrl === "string" ? options.sourceMapUrl : options.outSourceMap);
174    }
175
176    return {
177        code : stream + "",
178        map  : source_map
179    };
180};
181
182// UglifyJS.describe_ast = function() {
183//     function doitem(ctor) {
184//         var sub = {};
185//         ctor.SUBCLASSES.forEach(function(ctor){
186//             sub[ctor.TYPE] = doitem(ctor);
187//         });
188//         var ret = {};
189//         if (ctor.SELF_PROPS.length > 0) ret.props = ctor.SELF_PROPS;
190//         if (ctor.SUBCLASSES.length > 0) ret.sub = sub;
191//         return ret;
192//     }
193//     return doitem(UglifyJS.AST_Node).sub;
194// }
195
196UglifyJS.describe_ast = function() {
197    var out = UglifyJS.OutputStream({ beautify: true });
198    function doitem(ctor) {
199        out.print("AST_" + ctor.TYPE);
200        var props = ctor.SELF_PROPS.filter(function(prop){
201            return !/^\$/.test(prop);
202        });
203        if (props.length > 0) {
204            out.space();
205            out.with_parens(function(){
206                props.forEach(function(prop, i){
207                    if (i) out.space();
208                    out.print(prop);
209                });
210            });
211        }
212        if (ctor.documentation) {
213            out.space();
214            out.print_string(ctor.documentation);
215        }
216        if (ctor.SUBCLASSES.length > 0) {
217            out.space();
218            out.with_block(function(){
219                ctor.SUBCLASSES.forEach(function(ctor, i){
220                    out.indent();
221                    doitem(ctor);
222                    out.newline();
223                });
224            });
225        }
226    };
227    doitem(UglifyJS.AST_Node);
228    return out + "";
229};
230
231function readReservedFile(filename, reserved) {
232    if (!reserved) {
233        reserved = { vars: [], props: [] };
234    }
235    var data = fs.readFileSync(filename, "utf8");
236    data = JSON.parse(data);
237    if (data.vars) {
238        data.vars.forEach(function(name){
239            UglifyJS.push_uniq(reserved.vars, name);
240        });
241    }
242    if (data.props) {
243        data.props.forEach(function(name){
244            UglifyJS.push_uniq(reserved.props, name);
245        });
246    }
247    return reserved;
248}
249
250UglifyJS.readReservedFile = readReservedFile;
251
252UglifyJS.readDefaultReservedFile = function(reserved) {
253    return readReservedFile(require.resolve("./domprops.json"), reserved);
254};
255
256UglifyJS.readNameCache = function(filename, key) {
257    var cache = null;
258    if (filename) {
259        try {
260            var cache = fs.readFileSync(filename, "utf8");
261            cache = JSON.parse(cache)[key];
262            if (!cache) throw "init";
263            cache.props = UglifyJS.Dictionary.fromObject(cache.props);
264        } catch(ex) {
265            cache = {
266                cname: -1,
267                props: new UglifyJS.Dictionary()
268            };
269        }
270    }
271    return cache;
272};
273
274UglifyJS.writeNameCache = function(filename, key, cache) {
275    if (filename) {
276        var data;
277        try {
278            data = fs.readFileSync(filename, "utf8");
279            data = JSON.parse(data);
280        } catch(ex) {
281            data = {};
282        }
283        data[key] = {
284            cname: cache.cname,
285            props: cache.props.toObject()
286        };
287        fs.writeFileSync(filename, JSON.stringify(data, null, 2), "utf8");
288    }
289};
290
291// A file glob function that only supports "*" and "?" wildcards in the basename.
292// Example: "foo/bar/*baz??.*.js"
293// Argument `glob` may be a string or an array of strings.
294// Returns an array of strings. Garbage in, garbage out.
295UglifyJS.simple_glob = function simple_glob(glob) {
296    if (Array.isArray(glob)) {
297        return [].concat.apply([], glob.map(simple_glob));
298    }
299    if (glob.match(/\*|\?/)) {
300        var dir = path.dirname(glob);
301        try {
302            var entries = fs.readdirSync(dir);
303        } catch (ex) {}
304        if (entries) {
305            var pattern = "^" + path.basename(glob)
306                .replace(/[.+^$[\]\\(){}]/g, "\\$&")
307                .replace(/\*/g, "[^/\\\\]*")
308                .replace(/\?/g, "[^/\\\\]") + "$";
309            var mod = process.platform === "win32" ? "i" : "";
310            var rx = new RegExp(pattern, mod);
311            var results = entries.filter(function(name) {
312                return rx.test(name);
313            }).map(function(name) {
314                return path.join(dir, name);
315            });
316            if (results.length) return results;
317        }
318    }
319    return [ glob ];
320};
321