instance.js

"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    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) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const typescript_1 = __importDefault(require("typescript"));
const espree = __importStar(require("@synvert-hq/espree"));
const gonzales_pe_1 = __importDefault(require("@synvert-hq/gonzales-pe"));
const fs_1 = __importStar(require("fs"));
const path_1 = __importDefault(require("path"));
const debug_1 = __importDefault(require("debug"));
const configuration_1 = __importDefault(require("./configuration"));
const helper_1 = __importDefault(require("./helper"));
const scope_1 = require("./scope");
const condition_1 = require("./condition");
const utils_1 = require("./utils");
const node_mutation_1 = __importStar(require("@synvert-hq/node-mutation"));
const options_1 = require("./types/options");
const HtmlEngine = __importStar(require("./engines/html"));
const RailsErbEngine = __importStar(require("./engines/rails_erb"));
const DEFAULT_ENGINES = {
    ".html": HtmlEngine,
    ".erb": RailsErbEngine,
};
/**
 * Instance is an execution unit, it finds specified ast nodes,
 * checks if the nodes match some conditions, then insert, replace or delete code.
 * One instance can contains one or many Scope and Condition.
 * @property {string} filePath - file path to run instance
 * @borrows Instance#withinNodeSync as Instance#withNodeSync
 * @borrows Instance#findNodeSync as Instance#withNodeSync
 * @borrows Instance#withinNode as Instance#withNode
 * @borrows Instance#findNode as Instance#withNode
 */
class Instance {
    /**
     * Create an Instance
     * @param {string} filePath - file path
     * @param {Function} func - a function to find nodes, match conditions and rewrite code.
     */
    constructor(rewriter, filePath, func) {
        this.rewriter = rewriter;
        this.filePath = filePath;
        this.func = func;
        this.withNodeSync = this.withinNodeSync.bind(this);
        this.findNodeSync = this.withinNodeSync.bind(this);
        this.withNode = this.withinNode.bind(this);
        this.findNode = this.withinNode.bind(this);
        let strategy = node_mutation_1.Strategy.KEEP_RUNNING;
        node_mutation_1.default.configure({ strategy, tabWidth: configuration_1.default.tabWidth });
    }
    /**
     * Process one file.
     */
    processSync() {
        if (this.rewriter.options.parser === options_1.Parser.ESPREE &&
            [".ts", ".tsx"].includes(path_1.default.extname(this.filePath))) {
            return;
        }
        const currentFilePath = path_1.default.join(configuration_1.default.rootPath, this.filePath);
        if (configuration_1.default.showRunProcess) {
            console.log(this.filePath);
        }
        // It keeps running until no conflict.
        while (true) {
            const source = fs_1.default.readFileSync(currentFilePath, "utf-8");
            this.currentMutation = new node_mutation_1.default(source, {
                adapter: this.rewriter.parser,
            });
            try {
                const node = this.parseCode(currentFilePath, source);
                this.processWithNodeSync(node, this.func);
                const result = this.currentMutation.process();
                (0, debug_1.default)("synvert-core:process")(result);
                if (result.affected) {
                    this.rewriter.addAffectedFile(this.filePath);
                    fs_1.default.writeFileSync(currentFilePath, result.newSource);
                }
                if (!result.conflicted) {
                    break;
                }
            }
            catch (e) {
                console.log(e);
                if (e instanceof SyntaxError) {
                    console.log(`May not parse source code: ${source}`);
                }
                break;
            }
        }
    }
    /**
     * Process one file.
     * @async
     */
    process() {
        return __awaiter(this, void 0, void 0, function* () {
            if (this.rewriter.options.parser === options_1.Parser.ESPREE &&
                [".ts", ".tsx"].includes(path_1.default.extname(this.filePath))) {
                return;
            }
            const currentFilePath = path_1.default.join(configuration_1.default.rootPath, this.filePath);
            if (configuration_1.default.showRunProcess) {
                console.log(this.filePath);
            }
            // It keeps running until no conflict.
            while (true) {
                const source = yield fs_1.promises.readFile(currentFilePath, "utf-8");
                this.currentMutation = new node_mutation_1.default(source, {
                    adapter: this.rewriter.parser,
                });
                try {
                    const node = this.parseCode(currentFilePath, source);
                    yield this.processWithNode(node, this.func);
                    const result = this.currentMutation.process();
                    (0, debug_1.default)("synvert-core:process")(result);
                    if (result.affected) {
                        this.rewriter.addAffectedFile(this.filePath);
                        yield fs_1.promises.writeFile(currentFilePath, result.newSource);
                    }
                    if (!result.conflicted) {
                        break;
                    }
                }
                catch (e) {
                    console.log(e);
                    if (e instanceof SyntaxError) {
                        console.log(`May not parse source code: ${source}`);
                    }
                    break;
                }
            }
        });
    }
    /**
     * Test one file.
     * @returns {TestResultExt}
     */
    testSync() {
        if (this.rewriter.options.parser === options_1.Parser.ESPREE &&
            [".ts", ".tsx"].includes(path_1.default.extname(this.filePath))) {
            return {
                conflicted: false,
                affected: false,
                actions: [],
                filePath: this.filePath,
            };
        }
        const currentFilePath = path_1.default.join(configuration_1.default.rootPath, this.filePath);
        const source = fs_1.default.readFileSync(currentFilePath, "utf-8");
        this.currentMutation = new node_mutation_1.default(source, {
            adapter: this.rewriter.parser,
        });
        const node = this.parseCode(currentFilePath, source);
        this.processWithNodeSync(node, this.func);
        const result = this.currentMutation.test();
        result.filePath = this.filePath;
        (0, debug_1.default)("synvert-core:test")(result);
        return result;
    }
    /**
     * Test one file.
     * @async
     * @returns {Promise<TestResultExt>}
     */
    test() {
        return __awaiter(this, void 0, void 0, function* () {
            if (this.rewriter.options.parser === options_1.Parser.ESPREE &&
                [".ts", ".tsx"].includes(path_1.default.extname(this.filePath))) {
                return {
                    conflicted: false,
                    affected: false,
                    actions: [],
                    filePath: this.filePath,
                };
            }
            const currentFilePath = path_1.default.join(configuration_1.default.rootPath, this.filePath);
            const source = yield fs_1.promises.readFile(currentFilePath, "utf-8");
            this.currentMutation = new node_mutation_1.default(source, {
                adapter: this.rewriter.parser,
            });
            const node = this.parseCode(currentFilePath, source);
            yield this.processWithNode(node, this.func);
            const result = this.currentMutation.test();
            result.filePath = this.filePath;
            (0, debug_1.default)("synvert-core:test")(result);
            return result;
        });
    }
    /**
     * Set currentNode to node and process.
     * @param {T} node - set to current node
     * @param {Function} func
     */
    processWithNodeSync(node, func) {
        this.currentNode = node;
        func.call(this, this);
        this.currentNode = node;
    }
    /**
     * Set currentNode to node and process.
     * @async
     * @param {T} node - set to current node
     * @param {Function} func
     */
    processWithNode(node, func) {
        return __awaiter(this, void 0, void 0, function* () {
            this.currentNode = node;
            yield func.call(this, this);
            this.currentNode = node;
        });
    }
    /**
     * Set currentNode properly, process and set currentNode back to original currentNode.
     * @param {T} node - set to other node
     * @param {Function} func
     */
    processWithOtherNodeSync(node, func) {
        const originalNode = this.currentNode;
        this.currentNode = node;
        func.call(this, this);
        this.currentNode = originalNode;
    }
    /**
     * Set currentNode properly, process and set currentNode back to original currentNode.
     * @async
     * @param {T} node - set to other node
     * @param {Function} func
     */
    processWithOtherNode(node, func) {
        return __awaiter(this, void 0, void 0, function* () {
            const originalNode = this.currentNode;
            this.currentNode = node;
            yield func.call(this, this);
            this.currentNode = originalNode;
        });
    }
    /**
     * Get rewriter's parser.
     *
     * @returns {string} parser
     */
    get parser() {
        return this.rewriter.parser;
    }
    /**
     * Get currentMutation's adapter.
     *
     * @returns {MutationAdapter<T>}
     */
    get mutationAdapter() {
        return this.currentMutation.adapter;
    }
    /**
     * Create a {@link WithinScope} to recursively find matching ast nodes,
     * then continue operating on each matching ast node.
     * @example
     * // `$.ajax({ ... })` matches and call `foobar`
     * withinNodeSync({ nodeType: "CallExpression", callee: { nodeType: "MemberExpression", object: "$", property: "ajax" } }, () => { foobar })
     * withinNodeSync(".CallExpression[callee=.MemberExpression[object=$][property=ajax]]", () => { foobar });
     * @param nqlOrRules {string|Object} - to find matching ast nodes.
     * @param options {QueryOptions|Function} - query options.
     * @param func {Function} - to be called on the matching nodes.
     */
    withinNodeSync(nqlOrRules, options, func) {
        if (typeof options === "function") {
            new scope_1.WithinScope(this, nqlOrRules, {}, options).processSync();
        }
        else {
            new scope_1.WithinScope(this, nqlOrRules, options, func).processSync();
        }
    }
    /**
     * Create a {@link WithinScope} to recursively find matching ast nodes,
     * then continue operating on each matching ast node.
     * @async
     * @example
     * // `$.ajax({ ... })` matches and call `foobar`
     * await withinNode({ nodeType: "CallExpression", callee: { nodeType: "MemberExpression", object: "$", property: "ajax" } }, async () => { foobar })
     * await withinNode(".CallExpression[callee=.MemberExpression[object=$][property=ajax]]", async () => { foobar });
     * @param nqlOrRules {string|Object} - to find matching ast nodes.
     * @param options {QueryOptions|Function} - query options.
     * @param func {Function} - to be called on the matching nodes.
     */
    withinNode(nqlOrRules, options, func) {
        return __awaiter(this, void 0, void 0, function* () {
            if (typeof options === "function") {
                yield new scope_1.WithinScope(this, nqlOrRules, {}, options).process();
            }
            else {
                yield new scope_1.WithinScope(this, nqlOrRules, options, func).process();
            }
        });
    }
    /**
     * Create a {@link GotoScope} to go to a child node,
     * then continue operating on the child node.
     * @example
     * // `$.ajax({ ... })` goes to `$.ajax`
     * gotoNodeSync('callee', () => { })
     * @param {string} child_node_name - the name of the child nodes.
     * @param {Function} func - to continue operating on the matching nodes.
     */
    gotoNodeSync(childNodeName, func) {
        new scope_1.GotoScope(this, childNodeName, func).processSync();
    }
    /**
     * Create a {@link GotoScope} to go to a child node,
     * then continue operating on the child node.
     * @async
     * @example
     * // `$.ajax({ ... })` goes to `$.ajax`
     * await gotoNode('callee', async () => {})
     * @param {string} child_node_name - the name of the child nodes.
     * @param {Function} func - to continue operating on the matching nodes.
     */
    gotoNode(childNodeName, func) {
        return __awaiter(this, void 0, void 0, function* () {
            yield new scope_1.GotoScope(this, childNodeName, func).process();
        });
    }
    /**
     * Create a {@link IfExistCondition} to check if matching nodes exist in the child nodes,
     * if so, then continue operating on each matching ast node.
     * @example
     * // `class Foobar extends React.Component` matches and call `foobar`.
     * ifExistNodeSync({ nodeType: "ClassDeclaration", superClass: { nodeType: "MemberExpression", object: "React", property: "Component" } }, () => { foobar })
     * @param {string|Object} nqlOrRules - to check matching ast nodes.
     * @param {ConditionOptions<T>|Function} options - to do find in specific child node, e.g. { in: 'callee' }
     * @param {Function} func - call the function if the matching nodes exist in the child nodes.
     * @param {Function} elseFunc - call the else function if no matching node exists in the child nodes.
     */
    ifExistNodeSync(nqlOrRules, options, func, elseFunc) {
        if (typeof options === "function") {
            return new condition_1.IfExistCondition(this, nqlOrRules, {}, options, func).processSync();
        }
        return new condition_1.IfExistCondition(this, nqlOrRules, options, func, elseFunc).processSync();
    }
    /**
     * Create a {@link IfExistCondition} to check if matching nodes exist in the child nodes,
     * if so, then continue operating on each matching ast node.
     * @async
     * @example
     * // `class Foobar extends React.Component` matches and call `foobar`.
     * await ifExistNode({ nodeType: "ClassDeclaration", superClass: { nodeType: "MemberExpression", object: "React", property: "Component" } }, async () => { foobar })
     * @param {string|Object} nqlOrRules - to check matching ast nodes.
     * @param {ConditionOptions<T>|Function} options - to do find in specific child node, e.g. { in: 'callee' }
     * @param {Function} func - call the function if the matching nodes exist in the child nodes.
     * @param {Function} elseFunc - call the else function if no matching node exists in the child nodes.
     */
    ifExistNode(nqlOrRules, options, func, elseFunc) {
        return __awaiter(this, void 0, void 0, function* () {
            if (typeof options === "function") {
                return yield new condition_1.IfExistCondition(this, nqlOrRules, {}, options, func).process();
            }
            yield new condition_1.IfExistCondition(this, nqlOrRules, options, func, elseFunc).process();
        });
    }
    /**
     * Create a {@link UnlessExistCondition} to check if matching nodes does not exist in the child nodes,
     * if so, then continue operating on each matching ast node.
     * @example
     * // `class Foobar extends Component` matches and call `foobar`.
     * unlessExistNodeSync({ nodeType: "ClassDeclaration", superClass: { nodeType: "MemberExpression", object: "React", property: "Component" } }, () => {})
     * @param {string|Object} nqlOrRules - to check matching ast nodes.
     * @param {ConditionOptions<T>} options - to do find in specific child node, e.g. { in: 'callee' }
     * @param {Function} func - call the function if no matching node exists in the child nodes.
     * @param {Function} elseFunc - call the else function if the matching nodes exists in the child nodes.
     */
    unlessExistNodeSync(nqlOrRules, options, func, elseFunc) {
        if (typeof options === "function") {
            return new condition_1.UnlessExistCondition(this, nqlOrRules, {}, options, func).processSync();
        }
        return new condition_1.UnlessExistCondition(this, nqlOrRules, options, func, elseFunc).processSync();
    }
    /**
     * Create a {@link UnlessExistCondition} to check if matching nodes does not exist in the child nodes,
     * if so, then continue operating on each matching ast node.
     * @async
     * @example
     * // `class Foobar extends Component` matches and call `foobar`.
     * await unlessExistNode({ nodeType: "ClassDeclaration", superClass: { nodeType: "MemberExpression", object: "React", property: "Component" } }, async () => {})
     * @param {string|Object} nqlOrRules - to check matching ast nodes.
     * @param {ConditionOptions<T>} options - to do find in specific child node, e.g. { in: 'callee' }
     * @param {Function} func - call the function if no matching node exists in the child nodes.
     * @param {Function} elseFunc - call the else function if the matching nodes exists in the child nodes.
     */
    unlessExistNode(nqlOrRules, options, func, elseFunc) {
        return __awaiter(this, void 0, void 0, function* () {
            if (typeof options === "function") {
                return yield new condition_1.UnlessExistCondition(this, nqlOrRules, {}, options, func).process();
            }
            yield new condition_1.UnlessExistCondition(this, nqlOrRules, options, func, elseFunc).process();
        });
    }
    /**
     * Create a {@link IfOnlyExistCondition} to check if current node has only one child node and the child node matches rules,
     * if so, then continue operating on each matching ast node.
     * @example
     * // `class Foobar { foo() {} }` matches and call foobar, `class Foobar { foo() {}; bar() {}; }` does not match
     * ifOnlyExistNodeSync({ nodeType: "MethodDefinition", key: "foo" }, () => { foobar })
     * @param {string|Object} nqlOrRules - to check matching ast nodes.
     * @param {ConditionOptions<T>|Function} options - to do find in specific child node, e.g. { in: 'callee' }
     * @param {Function} func - call the function if the matching nodes exist in the child nodes.
     * @param {Function} elseFunc - call the else function if no matching node exists in the child nodes.
     */
    ifOnlyExistNodeSync(nqlOrRules, options, func, elseFunc) {
        if (typeof options === "function") {
            return new condition_1.IfOnlyExistCondition(this, nqlOrRules, {}, options, func).processSync();
        }
        return new condition_1.IfOnlyExistCondition(this, nqlOrRules, options, func, elseFunc).processSync();
    }
    /**
     * Create a {@link IfOnlyExistCondition} to check if current node has only one child node and the child node matches rules,
     * if so, then continue operating on each matching ast node.
     * @async
     * @example
     * // `class Foobar { foo() {} }` matches and call foobar, `class Foobar { foo() {}; bar() {}; }` does not match
     * await ifOnlyExistNode({ nodeType: "MethodDefinition", key: "foo" }, async () => { foobar })
     * @param {string|Object} nqlOrRules - to check matching ast nodes.
     * @param {ConditionOption|Function} options - to do find in specific child node, e.g. { in: 'callee' }
     * @param {Function} func - call the function if the matching nodes exist in the child nodes.
     * @param {Function} elseFunc - call the else function if no matching node exists in the child nodes.
     */
    ifOnlyExistNode(nqlOrRules, options, func, elseFunc) {
        return __awaiter(this, void 0, void 0, function* () {
            if (typeof options === "function") {
                return yield new condition_1.IfOnlyExistCondition(this, nqlOrRules, {}, options, func).process();
            }
            yield new condition_1.IfOnlyExistCondition(this, nqlOrRules, options, func, elseFunc).process();
        });
    }
    /**
     * Create a {@link IfAllCondition} to check if all matching nodes match options.match,
     * if so, then call the func, else call the elseFunc.
     * @example
     * // `class Foobar { foo() {}; bar() {}; }` matches and call foobar
     * ifAllNodesSync({ nodeType: "MethodDefinition" }, { match: { key: { in: ["foo", "bar"] } } }, () => { foo }, () => { bar });
     * @param {string|Object} nqlOrRules - to check matching ast nodes.
     * @param {ConditionOptions<T>|Function} options - { match: nqlOrRules, in: 'callee' }
     * @param {Function} func - call the function if the matching nodes match options.match.
     * @param {Function} elseFunc - call the else function if no matching node matches options.match.
     */
    ifAllNodesSync(nqlOrRules, options, func, elseFunc) {
        if (typeof options === "function") {
            return new condition_1.IfAllCondition(this, nqlOrRules, {}, options, func).processSync();
        }
        return new condition_1.IfAllCondition(this, nqlOrRules, options, func, elseFunc).processSync();
    }
    /**
     * Create a {@link IfAllCondition} to check if all matching nodes match options.match,
     * if so, then call the func, else call the elseFunc.
     * @async
     * @example
     * // `class Foobar { foo() {}; bar() {}; }` matches and call foobar
     * await ifAllNodes({ nodeType: "MethodDefinition" }, { match: { key: { in: ["foo", "bar"] } } }, () => { foo }, async () => { bar });
     * @param {string|Object} nqlOrRules - to check matching ast nodes.
     * @param {ConditionOptions<T>|Function} options - { match: nqlOrRules, in: 'callee' }
     * @param {Function} func - call the function if the matching nodes match options.match.
     * @param {Function} elseFunc - call the else function if no matching node matches options.match.
     */
    ifAllNodes(nqlOrRules, options, func, elseFunc) {
        return __awaiter(this, void 0, void 0, function* () {
            if (typeof options === "function") {
                return yield new condition_1.IfAllCondition(this, nqlOrRules, {}, options, func).process();
            }
            yield new condition_1.IfAllCondition(this, nqlOrRules, options, func, elseFunc).process();
        });
    }
    /**
     * Append the code to the bottom of current node body.
     * @example
     * // foo() => {}
     * // will be converted to
     * // foo() => {}
     * // bar() => {}
     * // after executing
     * withNode({ nodeType: "MethodDefinition", key: "foo" }, () => {
     *   append("bar() => {}")
     * })
     * @param {string} code - need to be appended.
     */
    append(code) {
        this.currentMutation.append(this.currentNode, code);
    }
    /**
     * Prepend the code to the top of current node body.
     * @example
     * // const foo = bar
     * // will be converted to
     * // 'use strict'
     * // const foo = bar
     * // after executing
     * prepend("'use strict'");
     * @param {string} code - need to be prepended.
     */
    prepend(code) {
        this.currentMutation.prepend(this.currentNode, code);
    }
    /**
     * Insert code to the beginning or end of the current node.
     * @example
     * // import React, { Component } from 'react'
     * // will be converted to
     * // import React, { Component, useState } from 'react'
     * // after executing
     * withNode({ nodeType: "ImportSpecifier", name: "Component" }, () => {
     *   insert(", useState", { at: "end" });
     * });
     * @param {string} code - code need to be inserted
     * @param {Object} options
     * @param {string} [options.at = "end"] - insert position, beginning or end
     * @param {string} [option.to] - selector to find the child ast node
     * @param {boolean} [option.andComma] - insert additional comma
     * @param {boolean} [option.andSpace] - insert additional space
     */
    insert(code, options) {
        this.currentMutation.insert(this.currentNode, code, options);
    }
    /**
     * Insert the code next to the current node.
     * @example
     * // import React from 'react'
     * // will be converted to
     * // import React from 'react'
     * // import PropTypes from 'prop-types'
     * // after executing
     * withNode({ nodeType: "ImportClause", name: "React" }, () => {
     *   insertAfter("import PropTypes from 'prop-types'");
     * });
     * @param {string} code - code need to be inserted
     * @param {Object} options
     * @param {string} [options.to] - selector to find the child ast node
     * @param {boolean} [option.andComma] - insert additional comma
     * @param {boolean} [option.andSpace] - insert additional space
     */
    insertAfter(code, options = {}) {
        const column = " ".repeat(this.currentMutation.adapter.getStartLoc(this.currentNode, options.to)
            .column);
        this.currentMutation.insert(this.currentNode, `\n${column}${code}`, Object.assign(Object.assign({}, options), { at: "end" }));
    }
    /**
     * Insert the code previous to the current node.
     * @example
     * // import React from 'react'
     * // will be converted to
     * // import PropTypes from 'prop-types'
     * // import React from 'react'
     * // after executing
     * withNode({ nodeType: "ImportClause", name: "React" }, () => {
     *   insertBefore("import PropTypes from 'prop-types'");
     * });
     * @param {string} code - code need to be inserted
     * @param {Object} options
     * @param {string} [options.to] - selector to find the child ast node
     * @param {boolean} [option.andComma] - insert additional comma
     * @param {boolean} [option.andSpace] - insert additional space
     */
    insertBefore(code, options = {}) {
        const column = " ".repeat(this.currentMutation.adapter.getStartLoc(this.currentNode, options.to)
            .column);
        this.currentMutation.insert(this.currentNode, `${code}\n${column}`, Object.assign(Object.assign({}, options), { at: "beginning" }));
    }
    /**
     * Delete child nodes.
     * @example
     * // const someObject = { cat: cat, dog: dog, bird: bird }
     * // will be converted to
     * // const someObject = { cat, dog, bird }
     * // after executing
     * withNode({ nodeType: "Property", key: { nodeType: "Identifier" }, value: { nodeType: "Identifier" } }, () => {
     *   delete(["semicolon", "value"]);
     * });
     * @param {string|string[]} selectors - name of child nodes
     * @param {Object} options
     * @param {boolean} [options.wholeLine = false] - remove the whole line
     * @param {boolean} [option.andComma] - delete additional comma
     */
    delete(selectors, options) {
        this.currentMutation.delete(this.currentNode, selectors, options);
    }
    /**
     * Remove current node.
     * @example
     * // class A {
     * //   constructor(props) {
     * //     super(props)
     * //   }
     * // }
     * // will be converted to
     * // class A {
     * // }
     * // after executing
     * withNode({ nodeType: "MethodDefinition", kind: "constructor" }, () => {
     *   remove();
     * });
     * @param {Object} options
     * @param {boolean} [option.andComma] - remove additional comma
     */
    remove(options) {
        this.currentMutation.remove(this.currentNode, options);
    }
    /**
     * Replace child nodes with code.
     * @example
     * // $form.submit();
     * // will be converted to
     * // $form.trigger('submit');
     * // after executing
     * withNode({ nodeType: "CallExpression", callee: { nodeType: "MemberExpression", object: /^\$/, property: 'submit' }, arguments: { length: 0 } }, () => {
     *   replace(["callee.property", "arguments"], { with: "trigger('submit')" });
     * });
     * @param {string|string[]} selectors - name of child nodes.
     * @param {Object} options
     * @param {string} options.with - new code to replace with
     */
    replace(selectors, options) {
        this.currentMutation.replace(this.currentNode, selectors, options);
    }
    /**
     * Replace current node with code.
     * @example
     * // module.exports = Rewriter
     * // will be converted to
     * // export default Rewriter
     * // after executing
     * withNode({ nodeType: "ExpressionStatement", expression: { nodeType: "AssignmentExpression", left: { nodeType: "MemberExpression", object: "module", property: "exports" }, right: { nodeType: "Identifier" } } }, () => {
     *   replaceWith("export default {{expression.right}}");
     * });
     * @param {string} code - code need to be replaced.
     */
    replaceWith(code) {
        this.currentMutation.replaceWith(this.currentNode, code);
    }
    /**
     * No operation.
     */
    noop() {
        this.currentMutation.noop(this.currentNode);
    }
    /**
     * Group actions.
     * @async
     * @example
     * group(() => {
     *   delete("leftCurlyBracket");
     *   delete("rightCurlyBracket", { wholeLine: true });
     * });
     * @param {Function} func
     */
    group(func) {
        return __awaiter(this, void 0, void 0, function* () {
            const result = this.currentMutation.group(func);
            if (result instanceof Promise) {
                yield result;
            }
        });
    }
    /**
     * Sync to call a helper to run shared code.
     * @param {string} helperName - snippet helper name, it can be a http url, file path or a helper name
     * @param options - options can be anything it needs to be passed to the helper
     */
    callHelperSync(helperName, options) {
        const helper = helper_1.default.fetch(helperName) || (0, utils_1.evalHelperSync)(helperName);
        if (!helper) {
            return;
        }
        helper.func.call(this, options);
    }
    /**
     * Async to call a helper to run shared code.
     * @async
     * @param {string} helperName - snippet helper name, it can be a http url, file path or a helper name
     * @param options - options can be anything it needs to be passed to the helper
     */
    callHelper(helperName, options) {
        return __awaiter(this, void 0, void 0, function* () {
            const helper = helper_1.default.fetch(helperName) || (yield (0, utils_1.evalHelper)(helperName));
            if (!helper) {
                return;
            }
            yield helper.func.call(this, options);
        });
    }
    /**
     * Wrap str string with single or double quotes based on Configuration.singleQuote.
     * @param {string} str string
     * @returns {string} quoted string
     */
    wrapWithQuotes(str) {
        const quote = configuration_1.default.singleQuote ? "'" : '"';
        const anotherQuote = configuration_1.default.singleQuote ? '"' : "'";
        if (str.indexOf(quote) !== -1 && str.indexOf(anotherQuote) === -1) {
            return `${anotherQuote}${str}${anotherQuote}`;
        }
        const escapedStr = str.replace(new RegExp(quote, "g"), `\\${quote}`);
        return `${quote}${escapedStr}${quote}`;
    }
    /**
     * Append semicolon to str if Configuration.semi is true.
     * @param {string} str string
     * @returns {string}
     */
    appendSemicolon(str) {
        if (configuration_1.default.semi && !str.endsWith(";")) {
            return `${str};`;
        }
        return str;
    }
    /**
     * Add leading spaces to the str according to Configuration.tabWidth.
     * @param {string} str string
     * @param {object} options
     * @param {number} options.tabSize tab size, default is 1
     * @returns {string}
     */
    addLeadingSpaces(str, { tabSize } = { tabSize: 1 }) {
        return " ".repeat(configuration_1.default.tabWidth * tabSize) + str;
    }
    /**
     * Indent each line in a string.
     * @example
     * //   foo
     * //   bar
     * indent("foo\nbar", 2)
     * @param {string} str
     * @param {number} spaceCount
     * @param {object} options
     * @param {number} options.skipFirstLine skip first line, default is false
     * @returns {string} indented str
     */
    indent(str, spaceCount, { skipFirstLine } = { skipFirstLine: false }) {
        let firstLine = true;
        return str
            .split("\n")
            .map((line, index) => {
            if (/^\s*$/.test(line)) {
                return line;
            }
            if (firstLine && skipFirstLine) {
                firstLine = !firstLine;
                return line;
            }
            if (spaceCount > 0) {
                return " ".repeat(spaceCount) + line;
            }
            else {
                return line.slice(-spaceCount);
            }
        })
            .join("\n");
    }
    /**
     * Parse code ast node.
     * @private
     * @param filePath {string} file path
     * @param source {string} file source
     * @returns {Node} ast node
     */
    parseCode(filePath, source) {
        if (this.rewriter.options.parser === options_1.Parser.GONZALES_PE) {
            return this.parseByGonzalesPe(filePath, source);
        }
        if (this.rewriter.options.parser === options_1.Parser.ESPREE) {
            return this.parseByEspree(filePath, source);
        }
        return this.parseByTypescript(filePath, source);
    }
    parseByGonzalesPe(filePath, source) {
        const syntax = path_1.default.extname(filePath).split(".").pop();
        return gonzales_pe_1.default.parse(source, { syntax, sourceFile: filePath });
    }
    /**
     * Parse by typescript.
     * @private
     * @param filePath {string} file path
     * @param source {string} file source
     * @returns {Node} ast node
     */
    parseByTypescript(filePath, source) {
        const scriptKind = [".js", ".jsx", ".html"].includes(path_1.default.extname(filePath))
            ? typescript_1.default.ScriptKind.JSX
            : typescript_1.default.ScriptKind.TSX;
        return typescript_1.default.createSourceFile(filePath, this.sourceToParse(filePath, source), typescript_1.default.ScriptTarget.Latest, true, scriptKind);
    }
    /**
     * Parse by espree.
     * @private
     * @param filePath {string} file path
     * @param source {string} file source
     * @returns {Node} ast node
     */
    parseByEspree(filePath, source) {
        return espree.parse(this.sourceToParse(filePath, source), {
            ecmaVersion: "latest",
            loc: true,
            sourceType: this.rewriter.options.sourceType,
            sourceFile: filePath,
            ecmaFeatures: { jsx: true },
        });
    }
    sourceToParse(filePath, source) {
        const engine = DEFAULT_ENGINES[path_1.default.extname(filePath)];
        return engine ? engine.encode(source) : source;
    }
}
exports.default = Instance;