adapter/espree.js

"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const fs_1 = __importDefault(require("fs"));
const error_1 = require("../error");
/**
 * Espree Adapter
 * @extends Adapter
 */
class EspreeAdapter {
    // get node source
    getSource(node, options) {
        const source = this.fileContent(node).slice(node.start, node.end);
        if (options && options.fixIndent) {
            const column = this.getIndent(node);
            return source
                .split("\n")
                .map((line, index) => {
                if (index === 0 || line === "") {
                    return line;
                }
                else {
                    const index = line.search(/\S|$/);
                    return index < column ? line.slice(index) : line.slice(column);
                }
            })
                .join("\n");
        }
        else {
            return source;
        }
    }
    /**
     * Get the source code of current file.
     * @returns {string} source code of current file.
     */
    fileContent(node) {
        return fs_1.default.readFileSync(node.loc.source, "utf-8");
    }
    /**
     * Get the source range of child node.
     * @param {Node} node - The node.
     * @param {string} childName - The name to find child node.
     * @returns {Object} The range of the child node, e.g. { start: 0, end: 10 }
     * @throws {NotSupportedError} if we can't get the range.
     * @example
     * const node = espree.parse("function foobar(foo, bar) {}")
     * childNodeRange(node, "id") // { start: "function ".length, end: "function foobar".length }
     *
     * // node array
     * const node = espree.parse("function foobar(foo, bar) {}")
     * childNodeRange(node, "params") // { start: "function foobar".length, end: "function foobar(foo, bar)".length }
     *
     * // index for node array
     * const node = espree.parse("function foobar(foo, bar) {}")
     * childNodeRange(node, "params.1") // { start: "function foobar(foo, ".length, end: "function foobar(foo, bar".length }
     *
     * // {name}Property for node who has properties
     * const node = espree.parse('const foobar = { foo: "foo", bar: "bar" }')
     * childNodeRange(node, "declarations.0.init.fooProperty") // { start: "const foobar = { ".length, end: "const foobar = { foo".length }
     *
     * // {name}Value for node who has properties
     * const node = espree.parse('const foobar = { foo: "foo", bar: "bar" }')
     * childNodeRange(node, 'declarations.0.init.fooValue')) // { start: "const foobar = { foo: ".length, end: "const foobar = { foo: "foo".length }
     *
     * // {name}Attribute for node who has attributes
     * const node = espree.parse('<Field name="email" autoComplete="email" />')
     * childNodeRange(node, "expression.openingElement.autoCompleteAttribute") // { start: '<Field name="email" '.length, end: '<Field name="email" autoComplete="email"'.length }
     *
     * // async for MethodDefinition node
     * const node = espree.parse("async foobar() {}")
     * childNodeRange(node, "async") // { start: 0, end: "async".length }
     *
     * // dot for MemberExpression node
     * const node = espree.parse("foo.bar")
     * childNodeRange(node, "dot") // { start: "foo".length, end: "foo.".length }
     *
     * // class for ClassDeclaration node
     * const node = espree.parse("class FooBar {}")
     * childNodeRange(node, "class") // { start: 0, end: "class".length }
     *
     * // semicolon for Property node
     * const node = espree.parse("{ foo: bar }");
     * childNodeRange(node, "semicolon") // { start: "{ foo", end: "{ foo:".length }
     */
    childNodeRange(node, childName) {
        if (node.type === "MethodDefinition" && childName === "async") {
            return { start: node.start, end: node.key.start };
        }
        else if (node.type === "MemberExpression" && childName === "dot") {
            return {
                start: node.property.start - 1,
                end: node.property.start,
            };
        }
        else if (["MemberExpression", "CallExpression"].includes(node.type) &&
            childName === "arguments") {
            if (node.arguments && node.arguments.length > 0) {
                return {
                    start: node.arguments[0].start - 1,
                    end: node.arguments[node.arguments.length - 1].end + 1,
                };
            }
            else {
                return { start: node.end - 2, end: node.end };
            }
        }
        else if (node.type === "ClassDeclaration" && childName === "class") {
            return { start: node.start, end: node.start + 5 };
        }
        else if (["FunctionDeclaration", "FunctionExpression"].includes(node.type) && childName === "params") {
            if (node.params && node.params.length > 0) {
                return {
                    start: node.params[0].start - 1,
                    end: node.params[node.params.length - 1].end + 1,
                };
            }
            else {
                return { start: node.end - 2, end: node.end };
            }
        }
        else if (node.type === "ImportDeclaration" &&
            childName === "specifiers") {
            return {
                start: node.start + this.getSource(node).indexOf("{"),
                end: node.start + this.getSource(node).indexOf("}") + 1,
            };
        }
        else if (node.type === "Property" && childName === "semicolon") {
            return {
                start: node.key.end,
                end: node.key.end + 1,
            };
        }
        else if (node.hasOwnProperty("properties") && childName.endsWith("Property")) {
            const property = node["properties"].find(property => this.getSource(property.key) == childName.slice(0, -"Property".length));
            return { start: property.start, end: property.end };
        }
        else if (node.hasOwnProperty("attributes") && childName.endsWith("Attribute")) {
            const attribute = node["attributes"].find(attribute => this.getSource(attribute.name) == childName.slice(0, -"Attribute".length));
            return { start: attribute.start, end: attribute.end };
        }
        else if (node.hasOwnProperty("properties") && childName.endsWith("Value")) {
            const property = node["properties"].find(property => this.getSource(property.key) == childName.slice(0, -"Value".length));
            return { start: property["value"].start, end: property["value"].end };
        }
        else {
            const [directChildName, ...nestedChildName] = childName.split(".");
            if (node[directChildName]) {
                const childNode = node[directChildName];
                if (Array.isArray(childNode)) {
                    const [childDirectChildName, ...childNestedChildName] = nestedChildName;
                    if (childNestedChildName.length > 0) {
                        return this.childNodeRange(childNode[childDirectChildName], childNestedChildName.join("."));
                    }
                    if (typeof childNode[childDirectChildName] === "function") {
                        const childChildNode = childNode[childDirectChildName].bind(childNode);
                        return { start: childChildNode().start, end: childChildNode().end };
                    }
                    else if (!Number.isNaN(childDirectChildName)) {
                        const childChildNode = childNode.at(Number.parseInt(childDirectChildName));
                        if (childChildNode) {
                            return { start: childChildNode.start, end: childChildNode.end };
                        }
                        else {
                            // arguments.0 for func()
                            return { start: node.end - 1, end: node.end - 1 };
                        }
                    }
                    else {
                        throw new error_1.NotSupportedError(`${childName} is not supported for ${this.getSource(node)}`);
                    }
                }
                if (nestedChildName.length > 0) {
                    return this.childNodeRange(childNode, nestedChildName.join("."));
                }
                if (childNode) {
                    return { start: childNode.start, end: childNode.end };
                }
            }
        }
        throw new error_1.NotSupportedError(`${childName} is not supported for ${this.getSource(node)}`);
    }
    /**
     * Get the value of child node.
     * @param {Node} node - The node to evaluate.
     * @param {string} childName - The name to find child node.
     * @returns {any} The value of child node, it can be a node, an array, a string or a number.
     * @example
     * const node = espree.parse("foobar(foo, bar)")
     * childNodeValue(node, "expression.arguments.0") // node["expression"]["arguments"][0]
     *
     * // node array
     * const node = espree.parse("foobar(foo, bar)")
     * childNodeValue(node, "expression.arguments") // node["expression"]["arguments"]
     *
     * // {name}Property for node who has properties
     * const node = espree.parse('const foobar = { foo: "foo", bar: "bar" }')
     * childNodeValue(node, "declarations.0.init.fooProperty") // node["declarations"][0]["init"]["properties"][0]
     *
     * // {name}Value for node who has properties
     * const node = espree.parse('const foobar = { foo: "foo", bar: "bar" }')
     * childNodeValue(node, 'declarations.0.init.fooValue')) // node["declarations"][0]["init"]["properties"][0]["value"]
     *
     * // {name}Attribute for node who has attributes
     * const node = espree.parse('<Field name="email" autoComplete="email" />')
     * childNodeValue(node, "expression.openingElement.autoCompleteAttribute") // node["expression"]["openingElement"]["attributes"][1]
     */
    childNodeValue(node, childName) {
        return this.actualValue(node, childName.split("."));
    }
    getStart(node, childName) {
        if (childName) {
            node = this.childNodeValue(node, childName);
        }
        return node.start;
    }
    getEnd(node, childName) {
        if (childName) {
            node = this.childNodeValue(node, childName);
        }
        return node.end;
    }
    getStartLoc(node, childName) {
        if (childName) {
            node = this.childNodeValue(node, childName);
        }
        const { line, column } = node.loc.start;
        return { line, column };
    }
    getEndLoc(node, childName) {
        if (childName) {
            node = this.childNodeValue(node, childName);
        }
        const { line, column } = node.loc.end;
        return { line, column };
    }
    getIndent(node) {
        return this.fileContent(node)
            .split("\n")[this.getStartLoc(node).line - 1].search(/\S|$/);
    }
    actualValue(node, multiKeys) {
        let childNode = node;
        multiKeys.forEach((key) => {
            if (!childNode)
                return;
            if (childNode.hasOwnProperty(key)) {
                childNode = childNode[key];
            }
            else if (Array.isArray(childNode) && /-?\d+/.test(key)) {
                childNode = childNode.at(Number.parseInt(key));
            }
            else if (childNode.hasOwnProperty("properties") && key.endsWith("Property")) {
                childNode = childNode.properties.find(property => this.getSource(property.key) == key.slice(0, -"Property".length));
            }
            else if (childNode.hasOwnProperty("attributes") && key.endsWith("Attribute")) {
                childNode = childNode.attributes.find(attribute => this.getSource(attribute.name) == key.slice(0, -"Attribute".length));
            }
            else if (childNode.hasOwnProperty("properties") && key.endsWith("Value")) {
                const property = childNode.properties.find(property => this.getSource(property.key) == key.slice(0, -"Value".length));
                childNode = property["value"];
            }
            else if (typeof childNode[key] === "function") {
                childNode = childNode[key].call(childNode);
            }
            else {
                throw `${key} is not supported for ${this.getSource(childNode)}`;
            }
        });
        return childNode;
    }
}
exports.default = EspreeAdapter;