'use strict';

Object.defineProperty(exports, '__esModule', { value: true });

var MagicString = require('magic-string');
var estreeWalker = require('estree-walker');
var compilerCore = require('@vue/compiler-core');
var parser = require('@babel/parser');
var shared = require('@vue/shared');

function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e['default'] : e; }

var MagicString__default = /*#__PURE__*/_interopDefaultLegacy(MagicString);

const CONVERT_SYMBOL = '$';
const ESCAPE_SYMBOL = '$$';
const shorthands = ['ref', 'computed', 'shallowRef', 'toRef', 'customRef'];
const transformCheckRE = /[^\w]\$(?:\$|ref|computed|shallowRef)?\s*(\(|\<)/;
function shouldTransform(src) {
    return transformCheckRE.test(src);
}
function transform(src, { filename, sourceMap, parserPlugins, importHelpersFrom = 'vue' } = {}) {
    const plugins = parserPlugins || [];
    if (filename) {
        if (/\.tsx?$/.test(filename)) {
            plugins.push('typescript');
        }
        if (filename.endsWith('x')) {
            plugins.push('jsx');
        }
    }
    const ast = parser.parse(src, {
        sourceType: 'module',
        plugins
    });
    const s = new MagicString__default(src);
    const res = transformAST(ast.program, s, 0);
    // inject helper imports
    if (res.importedHelpers.length) {
        s.prepend(`import { ${res.importedHelpers
            .map(h => `${h} as _${h}`)
            .join(', ')} } from '${importHelpersFrom}'\n`);
    }
    return {
        ...res,
        code: s.toString(),
        map: sourceMap
            ? s.generateMap({
                source: filename,
                hires: true,
                includeContent: true
            })
            : null
    };
}
function transformAST(ast, s, offset = 0, knownRefs, knownProps) {
    // TODO remove when out of experimental
    warnExperimental();
    let convertSymbol = CONVERT_SYMBOL;
    let escapeSymbol = ESCAPE_SYMBOL;
    // macro import handling
    for (const node of ast.body) {
        if (node.type === 'ImportDeclaration' &&
            node.source.value === 'vue/macros') {
            // remove macro imports
            s.remove(node.start + offset, node.end + offset);
            // check aliasing
            for (const specifier of node.specifiers) {
                if (specifier.type === 'ImportSpecifier') {
                    const imported = specifier.imported.name;
                    const local = specifier.local.name;
                    if (local !== imported) {
                        if (imported === ESCAPE_SYMBOL) {
                            escapeSymbol = local;
                        }
                        else if (imported === CONVERT_SYMBOL) {
                            convertSymbol = local;
                        }
                        else {
                            error(`macro imports for ref-creating methods do not support aliasing.`, specifier);
                        }
                    }
                }
            }
        }
    }
    const importedHelpers = new Set();
    const rootScope = {};
    const scopeStack = [rootScope];
    let currentScope = rootScope;
    let escapeScope; // inside $$()
    const excludedIds = new WeakSet();
    const parentStack = [];
    const propsLocalToPublicMap = Object.create(null);
    if (knownRefs) {
        for (const key of knownRefs) {
            rootScope[key] = true;
        }
    }
    if (knownProps) {
        for (const key in knownProps) {
            const { local } = knownProps[key];
            rootScope[local] = 'prop';
            propsLocalToPublicMap[local] = key;
        }
    }
    function isRefCreationCall(callee) {
        if (callee === convertSymbol) {
            return convertSymbol;
        }
        if (callee[0] === '$' && shorthands.includes(callee.slice(1))) {
            return callee;
        }
        return false;
    }
    function error(msg, node) {
        const e = new Error(msg);
        e.node = node;
        throw e;
    }
    function helper(msg) {
        importedHelpers.add(msg);
        return `_${msg}`;
    }
    function registerBinding(id, isRef = false) {
        excludedIds.add(id);
        if (currentScope) {
            currentScope[id.name] = isRef;
        }
        else {
            error('registerBinding called without active scope, something is wrong.', id);
        }
    }
    const registerRefBinding = (id) => registerBinding(id, true);
    let tempVarCount = 0;
    function genTempVar() {
        return `__$temp_${++tempVarCount}`;
    }
    function snip(node) {
        return s.original.slice(node.start + offset, node.end + offset);
    }
    function walkScope(node, isRoot = false) {
        for (const stmt of node.body) {
            if (stmt.type === 'VariableDeclaration') {
                walkVariableDeclaration(stmt, isRoot);
            }
            else if (stmt.type === 'FunctionDeclaration' ||
                stmt.type === 'ClassDeclaration') {
                if (stmt.declare || !stmt.id)
                    continue;
                registerBinding(stmt.id);
            }
            else if ((stmt.type === 'ForOfStatement' || stmt.type === 'ForInStatement') &&
                stmt.left.type === 'VariableDeclaration') {
                walkVariableDeclaration(stmt.left);
            }
            else if (stmt.type === 'ExportNamedDeclaration' &&
                stmt.declaration &&
                stmt.declaration.type === 'VariableDeclaration') {
                walkVariableDeclaration(stmt.declaration, isRoot);
            }
            else if (stmt.type === 'LabeledStatement' &&
                stmt.body.type === 'VariableDeclaration') {
                walkVariableDeclaration(stmt.body, isRoot);
            }
        }
    }
    function walkVariableDeclaration(stmt, isRoot = false) {
        if (stmt.declare) {
            return;
        }
        for (const decl of stmt.declarations) {
            let refCall;
            const isCall = decl.init &&
                decl.init.type === 'CallExpression' &&
                decl.init.callee.type === 'Identifier';
            if (isCall &&
                (refCall = isRefCreationCall(decl.init.callee.name))) {
                processRefDeclaration(refCall, decl.id, decl.init);
            }
            else {
                const isProps = isRoot && isCall && decl.init.callee.name === 'defineProps';
                for (const id of compilerCore.extractIdentifiers(decl.id)) {
                    if (isProps) {
                        // for defineProps destructure, only exclude them since they
                        // are already passed in as knownProps
                        excludedIds.add(id);
                    }
                    else {
                        registerBinding(id);
                    }
                }
            }
        }
    }
    function processRefDeclaration(method, id, call) {
        excludedIds.add(call.callee);
        if (method === convertSymbol) {
            // $
            // remove macro
            s.remove(call.callee.start + offset, call.callee.end + offset);
            if (id.type === 'Identifier') {
                // single variable
                registerRefBinding(id);
            }
            else if (id.type === 'ObjectPattern') {
                processRefObjectPattern(id, call);
            }
            else if (id.type === 'ArrayPattern') {
                processRefArrayPattern(id, call);
            }
        }
        else {
            // shorthands
            if (id.type === 'Identifier') {
                registerRefBinding(id);
                // replace call
                s.overwrite(call.start + offset, call.start + method.length + offset, helper(method.slice(1)));
            }
            else {
                error(`${method}() cannot be used with destructure patterns.`, call);
            }
        }
    }
    function processRefObjectPattern(pattern, call, tempVar, path = []) {
        if (!tempVar) {
            tempVar = genTempVar();
            // const { x } = $(useFoo()) --> const __$temp_1 = useFoo()
            s.overwrite(pattern.start + offset, pattern.end + offset, tempVar);
        }
        for (const p of pattern.properties) {
            let nameId;
            let key;
            let defaultValue;
            if (p.type === 'ObjectProperty') {
                if (p.key.start === p.value.start) {
                    // shorthand { foo }
                    nameId = p.key;
                    if (p.value.type === 'Identifier') {
                        // avoid shorthand value identifier from being processed
                        excludedIds.add(p.value);
                    }
                    else if (p.value.type === 'AssignmentPattern' &&
                        p.value.left.type === 'Identifier') {
                        // { foo = 1 }
                        excludedIds.add(p.value.left);
                        defaultValue = p.value.right;
                    }
                }
                else {
                    key = p.computed ? p.key : p.key.name;
                    if (p.value.type === 'Identifier') {
                        // { foo: bar }
                        nameId = p.value;
                    }
                    else if (p.value.type === 'ObjectPattern') {
                        processRefObjectPattern(p.value, call, tempVar, [...path, key]);
                    }
                    else if (p.value.type === 'ArrayPattern') {
                        processRefArrayPattern(p.value, call, tempVar, [...path, key]);
                    }
                    else if (p.value.type === 'AssignmentPattern') {
                        if (p.value.left.type === 'Identifier') {
                            // { foo: bar = 1 }
                            nameId = p.value.left;
                            defaultValue = p.value.right;
                        }
                        else if (p.value.left.type === 'ObjectPattern') {
                            processRefObjectPattern(p.value.left, call, tempVar, [
                                ...path,
                                [key, p.value.right]
                            ]);
                        }
                        else if (p.value.left.type === 'ArrayPattern') {
                            processRefArrayPattern(p.value.left, call, tempVar, [
                                ...path,
                                [key, p.value.right]
                            ]);
                        }
                        else ;
                    }
                }
            }
            else {
                // rest element { ...foo }
                error(`reactivity destructure does not support rest elements.`, p);
            }
            if (nameId) {
                registerRefBinding(nameId);
                // inject toRef() after original replaced pattern
                const source = pathToString(tempVar, path);
                const keyStr = shared.isString(key)
                    ? `'${key}'`
                    : key
                        ? snip(key)
                        : `'${nameId.name}'`;
                const defaultStr = defaultValue ? `, ${snip(defaultValue)}` : ``;
                s.appendLeft(call.end + offset, `,\n  ${nameId.name} = ${helper('toRef')}(${source}, ${keyStr}${defaultStr})`);
            }
        }
    }
    function processRefArrayPattern(pattern, call, tempVar, path = []) {
        if (!tempVar) {
            // const [x] = $(useFoo()) --> const __$temp_1 = useFoo()
            tempVar = genTempVar();
            s.overwrite(pattern.start + offset, pattern.end + offset, tempVar);
        }
        for (let i = 0; i < pattern.elements.length; i++) {
            const e = pattern.elements[i];
            if (!e)
                continue;
            let nameId;
            let defaultValue;
            if (e.type === 'Identifier') {
                // [a] --> [__a]
                nameId = e;
            }
            else if (e.type === 'AssignmentPattern') {
                // [a = 1]
                nameId = e.left;
                defaultValue = e.right;
            }
            else if (e.type === 'RestElement') {
                // [...a]
                error(`reactivity destructure does not support rest elements.`, e);
            }
            else if (e.type === 'ObjectPattern') {
                processRefObjectPattern(e, call, tempVar, [...path, i]);
            }
            else if (e.type === 'ArrayPattern') {
                processRefArrayPattern(e, call, tempVar, [...path, i]);
            }
            if (nameId) {
                registerRefBinding(nameId);
                // inject toRef() after original replaced pattern
                const source = pathToString(tempVar, path);
                const defaultStr = defaultValue ? `, ${snip(defaultValue)}` : ``;
                s.appendLeft(call.end + offset, `,\n  ${nameId.name} = ${helper('toRef')}(${source}, ${i}${defaultStr})`);
            }
        }
    }
    function pathToString(source, path) {
        if (path.length) {
            for (const seg of path) {
                if (shared.isArray(seg)) {
                    source = `(${source}${segToString(seg[0])} || ${snip(seg[1])})`;
                }
                else {
                    source += segToString(seg);
                }
            }
        }
        return source;
    }
    function segToString(seg) {
        if (typeof seg === 'number') {
            return `[${seg}]`;
        }
        else if (typeof seg === 'string') {
            return `.${seg}`;
        }
        else {
            return snip(seg);
        }
    }
    function rewriteId(scope, id, parent, parentStack) {
        if (shared.hasOwn(scope, id.name)) {
            const bindingType = scope[id.name];
            if (bindingType) {
                const isProp = bindingType === 'prop';
                if (compilerCore.isStaticProperty(parent) && parent.shorthand) {
                    // let binding used in a property shorthand
                    // skip for destructure patterns
                    if (!parent.inPattern ||
                        compilerCore.isInDestructureAssignment(parent, parentStack)) {
                        if (isProp) {
                            if (escapeScope) {
                                // prop binding in $$()
                                // { prop } -> { prop: __props_prop }
                                registerEscapedPropBinding(id);
                                s.appendLeft(id.end + offset, `: __props_${propsLocalToPublicMap[id.name]}`);
                            }
                            else {
                                // { prop } -> { prop: __props.prop }
                                s.appendLeft(id.end + offset, `: ${shared.genPropsAccessExp(propsLocalToPublicMap[id.name])}`);
                            }
                        }
                        else {
                            // { foo } -> { foo: foo.value }
                            s.appendLeft(id.end + offset, `: ${id.name}.value`);
                        }
                    }
                }
                else {
                    if (isProp) {
                        if (escapeScope) {
                            // x --> __props_x
                            registerEscapedPropBinding(id);
                            s.overwrite(id.start + offset, id.end + offset, `__props_${propsLocalToPublicMap[id.name]}`);
                        }
                        else {
                            // x --> __props.x
                            s.overwrite(id.start + offset, id.end + offset, shared.genPropsAccessExp(propsLocalToPublicMap[id.name]));
                        }
                    }
                    else {
                        // x --> x.value
                        s.appendLeft(id.end + offset, '.value');
                    }
                }
            }
            return true;
        }
        return false;
    }
    const propBindingRefs = {};
    function registerEscapedPropBinding(id) {
        if (!propBindingRefs.hasOwnProperty(id.name)) {
            propBindingRefs[id.name] = true;
            const publicKey = propsLocalToPublicMap[id.name];
            s.prependRight(offset, `const __props_${publicKey} = ${helper(`toRef`)}(__props, '${publicKey}')\n`);
        }
    }
    // check root scope first
    walkScope(ast, true);
    estreeWalker.walk(ast, {
        enter(node, parent) {
            parent && parentStack.push(parent);
            // function scopes
            if (compilerCore.isFunctionType(node)) {
                scopeStack.push((currentScope = {}));
                compilerCore.walkFunctionParams(node, registerBinding);
                if (node.body.type === 'BlockStatement') {
                    walkScope(node.body);
                }
                return;
            }
            // catch param
            if (node.type === 'CatchClause') {
                scopeStack.push((currentScope = {}));
                if (node.param && node.param.type === 'Identifier') {
                    registerBinding(node.param);
                }
                walkScope(node.body);
                return;
            }
            // non-function block scopes
            if (node.type === 'BlockStatement' && !compilerCore.isFunctionType(parent)) {
                scopeStack.push((currentScope = {}));
                walkScope(node);
                return;
            }
            // skip type nodes
            if (parent &&
                parent.type.startsWith('TS') &&
                parent.type !== 'TSAsExpression' &&
                parent.type !== 'TSNonNullExpression' &&
                parent.type !== 'TSTypeAssertion') {
                return this.skip();
            }
            if (node.type === 'Identifier' &&
                // if inside $$(), skip unless this is a destructured prop binding
                !(escapeScope && rootScope[node.name] !== 'prop') &&
                compilerCore.isReferencedIdentifier(node, parent, parentStack) &&
                !excludedIds.has(node)) {
                // walk up the scope chain to check if id should be appended .value
                let i = scopeStack.length;
                while (i--) {
                    if (rewriteId(scopeStack[i], node, parent, parentStack)) {
                        return;
                    }
                }
            }
            if (node.type === 'CallExpression' && node.callee.type === 'Identifier') {
                const callee = node.callee.name;
                const refCall = isRefCreationCall(callee);
                if (refCall && (!parent || parent.type !== 'VariableDeclarator')) {
                    return error(`${refCall} can only be used as the initializer of ` +
                        `a variable declaration.`, node);
                }
                if (callee === escapeSymbol) {
                    s.remove(node.callee.start + offset, node.callee.end + offset);
                    escapeScope = node;
                }
                // TODO remove when out of experimental
                if (callee === '$raw') {
                    error(`$raw() has been replaced by $$(). ` +
                        `See ${RFC_LINK} for latest updates.`, node);
                }
                if (callee === '$fromRef') {
                    error(`$fromRef() has been replaced by $(). ` +
                        `See ${RFC_LINK} for latest updates.`, node);
                }
            }
        },
        leave(node, parent) {
            parent && parentStack.pop();
            if ((node.type === 'BlockStatement' && !compilerCore.isFunctionType(parent)) ||
                compilerCore.isFunctionType(node)) {
                scopeStack.pop();
                currentScope = scopeStack[scopeStack.length - 1] || null;
            }
            if (node === escapeScope) {
                escapeScope = undefined;
            }
        }
    });
    return {
        rootRefs: Object.keys(rootScope).filter(key => rootScope[key] === true),
        importedHelpers: [...importedHelpers]
    };
}
const RFC_LINK = `https://github.com/vuejs/rfcs/discussions/369`;
const hasWarned = {};
function warnExperimental() {
    // eslint-disable-next-line
    if (typeof window !== 'undefined') {
        return;
    }
    warnOnce(`Reactivity transform is an experimental feature.\n` +
        `Experimental features may change behavior between patch versions.\n` +
        `It is recommended to pin your vue dependencies to exact versions to avoid breakage.\n` +
        `You can follow the proposal's status at ${RFC_LINK}.`);
}
function warnOnce(msg) {
    const isNodeProd = typeof process !== 'undefined' && process.env.NODE_ENV === 'production';
    if (!isNodeProd && !false && !hasWarned[msg]) {
        hasWarned[msg] = true;
        warn(msg);
    }
}
function warn(msg) {
    console.warn(`\x1b[1m\x1b[33m[@vue/reactivity-transform]\x1b[0m\x1b[33m ${msg}\x1b[0m\n`);
}

exports.shouldTransform = shouldTransform;
exports.transform = transform;
exports.transformAST = transformAST;
