"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
Object.defineProperty(exports, "__esModule", { value: true });
const ts = require("typescript");
const ast_1 = require("./ast");
const error_1 = require("./error");
function isCallToGlobal(node, fn) {
    if (!ts.isCallExpression(node) && !ts.isNewExpression(node)) {
        return undefined;
    }
    const expr = node.expression;
    // Check fn() calls
    if (ts.isIdentifier(expr)) {
        return expr.text === fn ? node.arguments : undefined;
    }
    // Check window.fn() calls
    if (ts.isPropertyAccessExpression(expr)) {
        if (ts.isIdentifier(expr.expression) &&
            expr.expression.text === 'window' &&
            ts.isIdentifier(expr.name) &&
            expr.name.text === fn) {
            return node.arguments;
        }
    }
    return undefined;
}
/**
 * Identifiers is a list of functions that finds eval methods. If one of these
 * returns true for a TypeScript node, the function is an eval.
 */
const identifiers = [
    /**
     * Simple eval() calls.
     */
        node => Boolean(isCallToGlobal(node, 'eval')),
    /**
     * new Function(...) calls
     */
        node => Boolean(isCallToGlobal(node, 'Function')),
    /**
     * setInterval/setTimeout with a string literal
     */
        node => {
        const call = isCallToGlobal(node, 'setInterval') || isCallToGlobal(node, 'setTimeout');
        if (!call || call.length === 0) {
            return false;
        }
        return ts.isLiteralExpression(call[0]) || ts.isTemplateLiteral(call[0]);
    },
];
/**
 * EvilSniffer walks the TypeScript project and errors if it sees dangerous
 * method like `eval`, `new Function`, `setTimeout(str)`, and so on.
 */
class EvilSniffer extends ast_1.TypeScriptWalker {
    visit(node) {
        if (identifiers.some(id => id(node))) {
            throw new error_1.DeclarationError(error_1.ErrorCode.DangerousEval, node, 'Executing arbitrary code is dangerous, particularly if the game client ' +
                'is compromised consider using an alternative strategy here.');
        }
        return ts.forEachChild(node, child => this.visit(child));
    }
}
exports.EvilSniffer = EvilSniffer;
/**
 * WriterReporter prompts the user whether to continue if dangerous
 * eval functions are found.
 */
class WriterReporter {
    constructor(project) {
        this.project = project;
    }
    /**
     * Checks that eval functions are not used, or asks the user to confirm
     * they they understand the risks using the writer. Returns whether
     * publishing should be continued.
     */
    checkEvil(writer) {
        return __awaiter(this, void 0, void 0, function* () {
            try {
                yield new EvilSniffer().compile(this.project.baseDir());
            }
            catch (e) {
                if (!(e instanceof error_1.DeclarationError)) {
                    throw e;
                }
                writer.write(`${e.getHumanMessage()}\n\n`);
                return yield writer.confirm('Using these unsafe functions is strongly discouraged, and may cause your ' +
                    'controls to be banned from Mixer. Read more about why this is at ' +
                    'https://aka.ms/dont-be-eval.', false);
            }
            return true;
        });
    }
}
exports.WriterReporter = WriterReporter;
