<commit_before>var util = require('util'),
    _ = require('underscore'),
    seq = require('seq'),
    uglify = require('uglify-js'),
    error = require('../error'),
    memoizeAsyncAccessor = require('../memoizeAsyncAccessor'),
    relations = require('../relations'),
    UrlMap = require('../UrlMap'),
    Text = require('./Text'),
    deepCopy = require('../deepCopy'),
    fileUtils = require('../fileUtils');

function JavaScript(config) {
    Text.call(this, config);
}

util.inherits(JavaScript, Text);

_.extend(JavaScript.prototype, {
    contentType: 'application/javascript', // TODO: Double check that this is everyone's recommended value

    defaultExtension: 'js',

    isPretty: false,

    getParseTree: memoizeAsyncAccessor('parseTree', function (cb) {
        var that = this;
        this.getDecodedSrc(error.passToFunction(cb, function (decodedSrc) {
            var parseTree;
            try {
                parseTree = uglify.parser.parse(decodedSrc);
            } catch (e) {
                return cb(new Error("Parse error in " + (that.url || "inline script") + "\n" +
                                    e.message + " (line " + e.line + ", col " + e.col + ")"));
            }
            cb(null, parseTree);
        }));
    }),

    minify: function (cb) {
        this.isPretty = false;
        this.isMinified = true;
        process.nextTick(cb);
    },

    prettyPrint: function (cb) {
        this.isPretty = true;
        process.nextTick(cb);
    },

    getText: function (cb) {
        var that = this;
        that.getParseTree(error.passToFunction(cb, function (parseTree) {
            if (that.isMinified) {
                parseTree = uglify.uglify.ast_squeeze(uglify.uglify.ast_mangle(parseTree), {no_warnings: true});
            }
            cb(null, uglify.uglify.gen_code(parseTree, that.isPretty));
        }));
    },

    getOriginalRelations: memoizeAsyncAccessor('originalRelations', function (cb) {
        var that = this;
        this.getParseTree(error.passToFunction(cb, function (parseTree) {
            cb = error.onlyCallOnce(cb);

            var stack = [],
                originalRelations = [];

            function traverse(node) {
                if (node[0] === 'stat' && Array.isArray(node[1]) &&
                    node[1][0] === 'call' && Array.isArray(node[1][1]) && node[1][1][0] === 'dot' &&
                    Array.isArray(node[1][1][1]) && node[1][1][1][0] === 'name' && node[1][1][1][1] === 'one') {

                    if (/^(?:lazyInlude|include)$/.test(node[1][1][2])) {
                        if (Array.isArray(node[1][2]) && node[1][2].length === 1 &&
                            Array.isArray(node[1][2][0]) && node[1][2][0][0] === 'string') {

                            originalRelations.push(new relations['JavaScript' + (node[1][1][2] === 'include' ? 'Static' : 'Lazy') + 'Include']({
                                from: that,
                                node: node,
                                parentNode: stack[stack.length - 1],
                                to: node[1][2][0][1]
                            }));
                        } else {
                            return cb(new Error("Invalid one.include syntax"));
                        }
                    }
                } else if (node[0] === 'call' && Array.isArray(node[1]) && node[1][0] === 'dot' &&
                           Array.isArray(node[1][1]) && node[1][1][0] === 'name' && node[1][1][1] === 'one' &&
                           node[1][2] === 'getStaticUrl') {

                    if (node.length >= 3 && Array.isArray(node[2]) && Array.isArray(node[2][0]) &&
                        node[2][0][0] === 'string') {
                        originalRelations.push(new relations.JavaScriptStaticUrl({
                            from: that,
                            urlMap: new UrlMap({
                                parentNode: stack[stack.length - 1],
                                node: node,
                                wildCardValueAsts: node[2].slice(1),
                                originalUrl: node[2][0][1]
                            }),
                            to: node[2][0][1]
                        }));
                    } else if (node.length === 3 && Array.isArray(node[2]) && Array.isArray(node[2][0]) && node[2][0][0] === 'sub') {
                        // Result of UrlMap.toExpressionAst
                        var urlMap = UrlMap.fromExpressionAst(node[2][0]);
                        urlMap.getReachableUrls().forEach(function (reachableUrl) {
                            originalRelations.push(new relations.JavaScriptStaticUrl({
                                from: that,
                                to: reachableUrl,
                                parentNode: stack[stack.length - 1],
                                node: node,
                                urlMap: urlMap
                            }));
                        });
                    } else {
                        return cb(new Error("Invalid one.getStaticUrl syntax: " + uglify.uglify.gen_code(node)));
                    }
                } else if (node[0] === 'if' && node[1][0] === 'dot' &&
                           node[1][1][0] === 'name' && node[1][1][1] === 'one') {
                    originalRelations.push(new relations.JavaScriptConditionalBlock({
                        from: that,
                        parentNode: stack[stack.length - 1],
                        node: node,
                        environment: node[1][2],
                        to: {
                            type: 'JavaScript',
                            rawSrc: true, // FIXME
                            parseTree: ['toplevel', [deepCopy(node[2])]]
                        }
                    }));
                }

                for (var i=0 ; i < node.length ; i++) {
                    if (Array.isArray(node[i])) {
                        stack.push(node);
                        traverse(node[i]);
                        stack.pop();
                    }
                }
            }
            traverse(parseTree);
            cb(null, originalRelations);
        }));
    }),

    attachRelation: function (relation, position, adjacentRelation) {
        position = position || 'after';
        _.extend(relation, {
            from: this,
            parentNode: adjacentRelation.parentNode
        });
        if (relation.type === 'JavaScriptStaticUrl') {
            if (!adjacentRelation) {
                throw new Error("Cannot attach a JavaScriptStaticUrl relation without an adjacent relation");
            }
            relation.urlMap = adjacentRelation.urlMap;
            relation.originalUrl = fileUtils.inferRelativeUrl(relation.urlMap.originalUrl, relation.to.url);
            relation.urlMap.addExpandedUrl(relation.originalUrl);
        } else {
            relation.node = relation.createNode(this.parseTree);
            var i = relation.parentNode.indexOf(adjacentRelation.node) + (position === 'after' ? 1 : 0);
            relation.parentNode.splice(i, 0, relation.node);
        }
    },

    detachRelation: function (relation) {
        if (relation.type === 'JavaScriptStaticUrl') {
            relation.urlMap.removeExpandedUrl(relation.originalUrl);
        } else {
            relation.parentNode.splice(relation.parentNode.indexOf(relation.node), 1);
            delete relation.node;
            delete relation.parentNode;
        }
    }
});

JavaScript.makeBundle = function (javaScripts, cb) {
    seq.ap(javaScripts).
        // Set a concurrency limit of 200 to avoid running out of file descriptors
        parMap(200, function (javaScript) {
            javaScript.getParseTree(this);
        })
        ['catch'](cb).
        seq(function () {
            cb(null, new JavaScript({
                parseTree: [
                    'toplevel',
                    Array.prototype.concat.apply([], this.stack.map(function (parseTree) {
                        return deepCopy(parseTree[1]);
                    }))<commit_msg>assets.JavaScript.makeBundle: Don't clone the parse trees of the assets being bundled, just Array.prototype.concat them together.<commit_after>var util = require('util'),
    _ = require('underscore'),
    seq = require('seq'),
    uglify = require('uglify-js'),
    error = require('../error'),
    memoizeAsyncAccessor = require('../memoizeAsyncAccessor'),
    relations = require('../relations'),
    UrlMap = require('../UrlMap'),
    Text = require('./Text'),
    deepCopy = require('../deepCopy'),
    fileUtils = require('../fileUtils');

function JavaScript(config) {
    Text.call(this, config);
}

util.inherits(JavaScript, Text);

_.extend(JavaScript.prototype, {
    contentType: 'application/javascript', // TODO: Double check that this is everyone's recommended value

    defaultExtension: 'js',

    isPretty: false,

    getParseTree: memoizeAsyncAccessor('parseTree', function (cb) {
        var that = this;
        this.getDecodedSrc(error.passToFunction(cb, function (decodedSrc) {
            var parseTree;
            try {
                parseTree = uglify.parser.parse(decodedSrc);
            } catch (e) {
                return cb(new Error("Parse error in " + (that.url || "inline script") + "\n" +
                                    e.message + " (line " + e.line + ", col " + e.col + ")"));
            }
            cb(null, parseTree);
        }));
    }),

    minify: function (cb) {
        this.isPretty = false;
        this.isMinified = true;
        process.nextTick(cb);
    },

    prettyPrint: function (cb) {
        this.isPretty = true;
        process.nextTick(cb);
    },

    getText: function (cb) {
        var that = this;
        that.getParseTree(error.passToFunction(cb, function (parseTree) {
            if (that.isMinified) {
                parseTree = uglify.uglify.ast_squeeze(uglify.uglify.ast_mangle(parseTree), {no_warnings: true});
            }
            cb(null, uglify.uglify.gen_code(parseTree, that.isPretty));
        }));
    },

    getOriginalRelations: memoizeAsyncAccessor('originalRelations', function (cb) {
        var that = this;
        this.getParseTree(error.passToFunction(cb, function (parseTree) {
            cb = error.onlyCallOnce(cb);

            var stack = [],
                originalRelations = [];

            function traverse(node) {
                if (node[0] === 'stat' && Array.isArray(node[1]) &&
                    node[1][0] === 'call' && Array.isArray(node[1][1]) && node[1][1][0] === 'dot' &&
                    Array.isArray(node[1][1][1]) && node[1][1][1][0] === 'name' && node[1][1][1][1] === 'one') {

                    if (/^(?:lazyInlude|include)$/.test(node[1][1][2])) {
                        if (Array.isArray(node[1][2]) && node[1][2].length === 1 &&
                            Array.isArray(node[1][2][0]) && node[1][2][0][0] === 'string') {

                            originalRelations.push(new relations['JavaScript' + (node[1][1][2] === 'include' ? 'Static' : 'Lazy') + 'Include']({
                                from: that,
                                node: node,
                                parentNode: stack[stack.length - 1],
                                to: node[1][2][0][1]
                            }));
                        } else {
                            return cb(new Error("Invalid one.include syntax"));
                        }
                    }
                } else if (node[0] === 'call' && Array.isArray(node[1]) && node[1][0] === 'dot' &&
                           Array.isArray(node[1][1]) && node[1][1][0] === 'name' && node[1][1][1] === 'one' &&
                           node[1][2] === 'getStaticUrl') {

                    if (node.length >= 3 && Array.isArray(node[2]) && Array.isArray(node[2][0]) &&
                        node[2][0][0] === 'string') {
                        originalRelations.push(new relations.JavaScriptStaticUrl({
                            from: that,
                            urlMap: new UrlMap({
                                parentNode: stack[stack.length - 1],
                                node: node,
                                wildCardValueAsts: node[2].slice(1),
                                originalUrl: node[2][0][1]
                            }),
                            to: node[2][0][1]
                        }));
                    } else if (node.length === 3 && Array.isArray(node[2]) && Array.isArray(node[2][0]) && node[2][0][0] === 'sub') {
                        // Result of UrlMap.toExpressionAst
                        var urlMap = UrlMap.fromExpressionAst(node[2][0]);
                        urlMap.getReachableUrls().forEach(function (reachableUrl) {
                            originalRelations.push(new relations.JavaScriptStaticUrl({
                                from: that,
                                to: reachableUrl,
                                parentNode: stack[stack.length - 1],
                                node: node,
                                urlMap: urlMap
                            }));
                        });
                    } else {
                        return cb(new Error("Invalid one.getStaticUrl syntax: " + uglify.uglify.gen_code(node)));
                    }
                } else if (node[0] === 'if' && node[1][0] === 'dot' &&
                           node[1][1][0] === 'name' && node[1][1][1] === 'one') {
                    originalRelations.push(new relations.JavaScriptConditionalBlock({
                        from: that,
                        parentNode: stack[stack.length - 1],
                        node: node,
                        environment: node[1][2],
                        to: {
                            type: 'JavaScript',
                            rawSrc: true, // FIXME
                            parseTree: ['toplevel', [deepCopy(node[2])]]
                        }
                    }));
                }

                for (var i=0 ; i < node.length ; i++) {
                    if (Array.isArray(node[i])) {
                        stack.push(node);
                        traverse(node[i]);
                        stack.pop();
                    }
                }
            }
            traverse(parseTree);
            cb(null, originalRelations);
        }));
    }),

    attachRelation: function (relation, position, adjacentRelation) {
        position = position || 'after';
        _.extend(relation, {
            from: this,
            parentNode: adjacentRelation.parentNode
        });
        if (relation.type === 'JavaScriptStaticUrl') {
            if (!adjacentRelation) {
                throw new Error("Cannot attach a JavaScriptStaticUrl relation without an adjacent relation");
            }
            relation.urlMap = adjacentRelation.urlMap;
            relation.originalUrl = fileUtils.inferRelativeUrl(relation.urlMap.originalUrl, relation.to.url);
            relation.urlMap.addExpandedUrl(relation.originalUrl);
        } else {
            relation.node = relation.createNode(this.parseTree);
            var i = relation.parentNode.indexOf(adjacentRelation.node) + (position === 'after' ? 1 : 0);
            relation.parentNode.splice(i, 0, relation.node);
        }
    },

    detachRelation: function (relation) {
        if (relation.type === 'JavaScriptStaticUrl') {
            relation.urlMap.removeExpandedUrl(relation.originalUrl);
        } else {
            relation.parentNode.splice(relation.parentNode.indexOf(relation.node), 1);
            delete relation.node;
            delete relation.parentNode;
        }
    }
});

JavaScript.makeBundle = function (javaScripts, cb) {
    seq.ap(javaScripts).
        // Set a concurrency limit of 200 to avoid running out of file descriptors
        parMap(200, function (javaScript) {
            javaScript.getParseTree(this);
        })
        ['catch'](cb).
        seq(function () {
            cb(null, new JavaScript({
                parseTree: [
                    'toplevel',
                    Array.prototype.concat.apply([], this.stack.map(function (parseTree) {
                        return parseTree[1];
                    }))
{'fraction_non_alphanumeric': 0.14030581039755352, 'fraction_numerical': 0.013211009174311927, 'mean_word_length': 1.4353589514447422, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 6, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4298945', 'n_tokens_mistral': 4457, 'n_tokens_neox': 4253, 'n_words': 786}