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