Object.defineProperty(exports, "__esModule", { value: true });
const FunctionNames = require("./FunctionNames");
const getBabelOptions_1 = require("./getBabelOptions");
const config_1 = require("./config");
let t;
// This file is also imported into helperFunctions, i.e. FE code that can't load
// Babel dependencies
function initForBabel(babelTypes) {
    t = babelTypes;
}
exports.initForBabel = initForBabel;
// declare module "@babel/types" {
//   // Should just be interface Node, but somehow it only works if
//   // I do it separately for each one...
//   interface StringLiteral {
//     ignore: boolean;
//   }
//   interface CallExpression {
//     ignore: boolean;
//   }
//   interface NumericLiteral {
//     ignore: boolean;
//   }
//   interface Identifier {
//     ignore: boolean;
//   }
// }
function addLoc(node, loc) {
    node.loc = loc;
    if (!loc) {
        debugger;
    }
    return node;
}
exports.addLoc = addLoc;
function ignoreNode(node) {
    node.ignore = true;
    // node.__debugIgnore = Error().stack
    return node;
}
exports.ignoreNode = ignoreNode;
function ignoredArrayExpression(items) {
    return ignoreNode(t.arrayExpression(items));
}
exports.ignoredArrayExpression = ignoredArrayExpression;
function ignoredStringLiteral(str) {
    return skipPath(t.stringLiteral(str));
}
exports.ignoredStringLiteral = ignoredStringLiteral;
function ignoredIdentifier(name) {
    return skipPath(t.identifier(name));
}
exports.ignoredIdentifier = ignoredIdentifier;
function ignoredCallExpression(identifier, args) {
    return ignoreNode(t.callExpression(ignoredIdentifier(identifier), args));
}
exports.ignoredCallExpression = ignoredCallExpression;
function ignoredNumericLiteral(number) {
    return skipPath(t.numericLiteral(number));
}
exports.ignoredNumericLiteral = ignoredNumericLiteral;
const canBeIdentifierRegExp = /^[a-z0-9A-Z]+$/;
const objectIdCache = {};
function getObjectId(name) {
    // Cache here for improved compilation performance
    // Not caching at the ignoredIdentifier/IgnoredStringLiteral level because
    // sometimes the result is modified to add a .loc (which isn't needed right
    // now I think but if we have sourcemaps in the future it will be needed)
    if (!objectIdCache[name]) {
        if (canBeIdentifierRegExp.test(name)) {
            objectIdCache[name] = ignoredIdentifier(name);
        }
        else {
            objectIdCache[name] = ignoredStringLiteral(name);
        }
    }
    return objectIdCache[name];
}
function ignoredArrayExpressionIfArray(p) {
    return Array.isArray(p) ? ignoredArrayExpression(p) : p;
}
exports.ignoredArrayExpressionIfArray = ignoredArrayExpressionIfArray;
function ignoredObjectExpression(props) {
    const properties = Object.keys(props).map(propKey => {
        return ignoreNode(t.objectProperty(getObjectId(propKey), ignoredArrayExpressionIfArray(props[propKey])));
    });
    return ignoreNode(t.objectExpression(properties));
}
exports.ignoredObjectExpression = ignoredObjectExpression;
function skipPath(node) {
    // skips the entire path, i.e. also all children!
    node.skipPath = true;
    return node;
}
exports.skipPath = skipPath;
function getLocObjectASTNode(loc) {
    const DISABLE_LOC_FOR_DEBUGGING = false || config_1.SKIP_TRACKING;
    if (DISABLE_LOC_FOR_DEBUGGING) {
        return ignoreNode(t.nullLiteral());
    }
    loc.url = getBabelOptions_1.getCurrentBabelFilePath();
    const locId = getBabelOptions_1.createLoc(loc);
    return ignoredStringLiteral(locId);
}
exports.getLocObjectASTNode = getLocObjectASTNode;
function getBabelTypes() {
    return t;
}
exports.getBabelTypes = getBabelTypes;
let noLocCount = 0;
function createOperation(opType, opArgs, astArgs = null, locArg = null, shorthand = null) {
    if (!locArg && config_1.VERIFY) {
        noLocCount++;
        console.log("no loc for", opType, noLocCount);
    }
    const loc = locArg || {};
    if ("type" in opArgs) {
        throw Error("should not put node into createOp, use array or obj");
    }
    let locAstNode;
    if (!loc || config_1.SKIP_TRACKING) {
        locAstNode = t.nullLiteral();
    }
    else {
        locAstNode = getLocObjectASTNode(loc);
    }
    if (shorthand) {
        const call = shorthand["visitor"](opArgs, astArgs, locAstNode);
        if (call) {
            return call;
        }
    }
    const args = [
        Array.isArray(opArgs)
            ? ignoredArrayExpression(opArgs.map(a => {
                return Array.isArray(a) ? ignoredArrayExpression(a) : a;
            }))
            : ignoredObjectExpression(opArgs),
        astArgs !== null
            ? skipPath(ignoredObjectExpression(astArgs))
            : t.nullLiteral()
    ];
    if (loc && !config_1.SKIP_TRACKING) {
        args.push(locAstNode);
    }
    var call = ignoredCallExpression("__" + opType, args);
    call.skipKeys = {
        callee: true
    };
    if (loc) {
        call.loc = loc;
    }
    return call;
}
exports.createOperation = createOperation;
exports.getLastOperationTrackingResultCall = () => skipPath(ignoredCallExpression(FunctionNames.getLastOperationTrackingResult, []));
exports.getLastOperationTrackingResultWithoutResettingCall = () => skipPath(ignoredCallExpression(FunctionNames.getLastOperationTrackingResultWithoutResetting, []));
function isInLeftPartOfAssignmentExpression(path) {
    return isInNodeType("AssignmentExpression", path, function (path, prevPath) {
        return path.node.left === prevPath.node;
    });
}
exports.isInLeftPartOfAssignmentExpression = isInLeftPartOfAssignmentExpression;
function isInIdOfVariableDeclarator(path) {
    return isInNodeType("VariableDeclarator", path, function (path, prevPath) {
        return path.node.id === prevPath.node;
    });
}
exports.isInIdOfVariableDeclarator = isInIdOfVariableDeclarator;
function getTrackingVarName(identifierName) {
    return identifierName + "___tv";
}
exports.getTrackingVarName = getTrackingVarName;
function getTrackingIdentifier(identifierName) {
    return ignoredIdentifier(getTrackingVarName(identifierName));
}
exports.getTrackingIdentifier = getTrackingIdentifier;
function trackingIdentifierIfExists(identifierName) {
    var trackingIdentifierName = getTrackingVarName(identifierName);
    return skipPath(runIfIdentifierExists(trackingIdentifierName, getTrackingIdentifier(identifierName)));
}
exports.trackingIdentifierIfExists = trackingIdentifierIfExists;
function isInNodeType(type, path, extraCondition = null, prevPath = null) {
    if (prevPath === null) {
        isInNodeType(type, path.parentPath, extraCondition, path);
    }
    if (path.node.type === "Program") {
        return false;
    }
    if (path.node.type === type) {
        if (!extraCondition || extraCondition(path, prevPath)) {
            return true;
        }
    }
    if (path.parentPath) {
        return isInNodeType(type, path.parentPath, extraCondition, path);
    }
    throw Error("unreachable");
}
exports.isInNodeType = isInNodeType;
function runIfIdentifierExists(identifierName, thenNode) {
    const iN = ignoreNode;
    return iN(t.logicalExpression("&&", iN(skipPath(t.binaryExpression("!==", iN(t.unaryExpression("typeof", ignoredIdentifier(identifierName))), ignoredStringLiteral("undefined")))), thenNode));
}
exports.runIfIdentifierExists = runIfIdentifierExists;
function createSetMemoValue(key, value, trackingValue) {
    return ignoredCallExpression(FunctionNames.setMemoValue, [
        ignoredStringLiteral(key),
        value,
        trackingValue
    ]);
}
exports.createSetMemoValue = createSetMemoValue;
function createGetMemoArray(key) {
    return skipPath(ignoredCallExpression(FunctionNames.getMemoArray, [
        ignoredStringLiteral(key)
    ]));
}
exports.createGetMemoArray = createGetMemoArray;
function createGetMemoValue(key) {
    return skipPath(ignoredCallExpression(FunctionNames.getMemoValue, [
        ignoredStringLiteral(key)
    ]));
}
exports.createGetMemoValue = createGetMemoValue;
function createGetMemoTrackingValue(key) {
    return skipPath(ignoredCallExpression(FunctionNames.getMemoTrackingValue, [
        ignoredStringLiteral(key)
    ]));
}
exports.createGetMemoTrackingValue = createGetMemoTrackingValue;
exports.getLastOpValueCall = () => ignoredCallExpression(FunctionNames.getLastOperationValueResult, []);
exports.safelyGetVariableTrackingValue = (identifierName, scope) => {
    const binding = scope.getBinding(identifierName);
    if (binding && ["var", "let", "const", "param"].includes(binding.kind)) {
        return getTrackingIdentifier(identifierName);
    }
    else {
        // If the value has been declared as a var then we know the
        // tracking var also exists,
        // otherwise we have to confirm it exists at runtime before
        // trying to access it
        return trackingIdentifierIfExists(identifierName);
    }
};
exports.getGetGlobalCall = () => {
    return ignoredCallExpression(FunctionNames.getGlobal, []);
};
//# sourceMappingURL=babelPluginHelpers.js.map