"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;