adapter/typescript.js

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const typescript_1 = require("typescript");
const error_1 = require("../error");
/**
 * Typescript Adapter
 * @extends Adapter
 */
class TypescriptAdapter {
    getSource(node, options) {
        if (options && options.fixIndent) {
            const column = this.getIndent(node);
            return node.getText().trimEnd()
                .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 {
            // typescript getText() may contain trailing whitespaces and newlines.
            return node.getText().trimEnd();
        }
    }
    fileContent(node) {
        return node.getSourceFile().getFullText();
    }
    /**
     * 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 = ts.createSourceFile("code.ts", "function foobar(foo, bar) {}")
     * childNodeRange(node, "name") // { start: "function ".length, end: "function foobar".length }
     *
     * // node array
     * const node = ts.createSourceFile("code.ts", "function foobar(foo, bar) {}")
     * childNodeRange(node, "parameters") // { start: "function foobar".length, end: "function foobar(foo, bar)".length }
     *
     * // index for node array
     * const node = ts.createSourceFile("code.ts", "function foobar(foo, bar) {}")
     * childNodeRange(node, "parameters.1") // { start: "function foobar(foo, ".length, end: "function foobar(foo, bar".length }
     *
     * // {name}Property for node who has properties
     * const node = ts.createSourceFile("code.ts", "const foobar = { foo: 'foo', bar: 'bar' }")
     * childNodeRange(node, "declarationList.declarations.0.initializer.fooProperty") // { start: "const foobar = { ".length, end: 'const foobar = { foo: "foo"'.length }
     *
     * // {name}Value for node who has properties
     * const node = ts.createSourceFile("code.ts", "const foobar = { foo: 'foo', bar: 'bar' }")
     * childNodeRange(node, "declarationList.declarations.0.initializer.fooValue") // { start: "const foobar = { foo: ".length, end: 'const foobar = { foo: "foo"'.length }
     *
     * // {name}Attribute for jsx node who has properties
     * const node = ts.createSourceFile("code.tsx", '<Field name="email" autoComplete="email" />')
     * childNodeRange(node, "expression.attriutes.autoCompleteAttribute") // { start: '<Field name="email" '.length, end: '<Field name="email" autoComplete="email"'.length }
     *
     * // semicolon for PropertyAssignment node
     * const node = ts.createSourceFile("code.ts", "{ foo: bar }");
     * childNodeRange(node, "semicolon") // { start: "{ foo", end: "{ foo:".length }
     *
     * // dot for PropertyAccessExpression node
     * const node = ts.createSourceFile("code.ts", "foo.bar")
     * childNodeRange(node, "dot") // { start: "foo".length, end: "foo.".length }
     */
    childNodeRange(node, childName) {
        if (["arguments", "parameters"].includes(childName)) {
            const elements = node[childName];
            if (Array.isArray(elements) && elements.hasOwnProperty('0')) {
                return { start: this.getStart(elements[0]) - 1, end: this.getEnd(elements[elements.length - 1]) + 1 };
            }
            else {
                return { start: elements['pos'] - 1, end: elements['pos'] + 1 };
            }
        }
        else if (node.kind === typescript_1.SyntaxKind.PropertyAssignment && childName === "semicolon") {
            return { start: this.getEnd(node.name), end: this.getEnd(node.name) + 1 };
        }
        else if (node.kind === typescript_1.SyntaxKind.PropertyAccessExpression && childName === "dot") {
            return { start: this.getStart(node.name) - 1, end: this.getStart(node.name) };
        }
        else if (node.hasOwnProperty("properties") && childName.endsWith("Property")) {
            const property = node["properties"].find(property => this.getSource(property.name) == childName.slice(0, -"Property".length));
            return { start: this.getStart(property), end: this.getEnd(property) };
        }
        else if (node.hasOwnProperty("properties") && childName.endsWith("Attribute")) {
            const property = node["properties"].find(property => this.getSource(property.name) == childName.slice(0, -"Attribute".length));
            return { start: this.getStart(property), end: this.getEnd(property) };
        }
        else if (node.hasOwnProperty("properties") && childName.endsWith("Initializer")) {
            const property = node["properties"].find(property => this.getSource(property.name) == childName.slice(0, -"Initializer".length));
            return { start: this.getStart(property === null || property === void 0 ? void 0 : property.initializer), end: this.getEnd(property === null || property === void 0 ? void 0 : property.initializer) };
        }
        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]();
                        return { start: this.getStart(childChildNode), end: this.getEnd(childChildNode) };
                    }
                    else if (!Number.isNaN(childDirectChildName)) {
                        const childChildNode = childNode.at(Number.parseInt(childDirectChildName));
                        if (childChildNode) {
                            return { start: this.getStart(childChildNode), end: this.getEnd(childChildNode) };
                        }
                        else {
                            return { start: this.getEnd(node) - 1, end: this.getEnd(node) - 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: this.getStart(childNode), end: this.getEnd(childNode) };
                }
            }
        }
        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 = ts.createSourceFile("code.ts", "foobar(foo, bar)")
     * childNodeValue(node, "expression.arguments.0") // node["expression"]["arguments"][0]
     *
     * // node array
     * const node = ts.createSourceFile("code.ts", 'foobar("foo", "bar")')
     * childNodeValue(node, "expression.arguments") // node["expression"]["arguments"]
     *
     * // {name}Property for node who has properties
     * const node = ts.createSourceFile("code.ts", "const foobar = { foo: 'foo', bar: 'bar' }")
     * childNodeValue(node, 'declarationList.declarations.0.initializer.fooProperty')) // node["declarationList"]["declarations"][0]["initializer"]["properties"][0]
     *
     * // {name}Initializer for node who has properties
     * const node = ts.createSourceFile("code.ts", "const foobar = { foo: 'foo', bar: 'bar' }")
     * childNodeValue(node, 'declarationList.declarations.0.initializer.fooInitializer')) // node["declarationList"]["declarations"][0]["initializer"]["properties"][0]["initalizer"]
     *
     * // {name}Attribute for jsx node who has properties
     * const node = ts.createSourceFile("code.tsx", '<Field name="email" autoComplete="email" />')
     * childNodeValue(node, 'expression.attributes.autoCompleteAttribute')) // node["exression"]["attributes"]["properties"][1]
     */
    childNodeValue(node, childName) {
        return this.actualValue(node, childName.split("."));
    }
    getStart(node, childName) {
        if (childName) {
            node = this.childNodeValue(node, childName);
        }
        return node.getStart();
    }
    getEnd(node, childName) {
        if (childName) {
            node = this.childNodeValue(node, childName);
        }
        // typescript getText() may contain trailing whitespaces and newlines.
        const trailingLength = node.getText().length - node.getText().trimEnd().length;
        return node.getEnd() - trailingLength;
    }
    getStartLoc(node, childName) {
        if (childName) {
            node = this.childNodeValue(node, childName);
        }
        const { line, character } = node.getSourceFile().getLineAndCharacterOfPosition(this.getStart(node));
        return { line: line + 1, column: character };
    }
    getEndLoc(node, childName) {
        if (childName) {
            node = this.childNodeValue(node, childName);
        }
        const { line, character } = node.getSourceFile().getLineAndCharacterOfPosition(this.getEnd(node));
        return { line: line + 1, column: character };
    }
    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.name) == key.slice(0, -"Property".length));
            }
            else if (childNode.hasOwnProperty("properties") && key.endsWith("Attribute")) {
                childNode = childNode.properties.find(property => this.getSource(property.name) == key.slice(0, -"Attribute".length));
            }
            else if (childNode.hasOwnProperty("properties") && key.endsWith("Initializer")) {
                const property = childNode.properties.find(property => this.getSource(property.name) == key.slice(0, -"Initializer".length));
                childNode = property.initializer;
            }
            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 = TypescriptAdapter;