"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 });
exports.isValidFile = exports.isValidFileSync = exports.loadSnippet = exports.loadSnippetSync = exports.evalHelper = exports.evalHelperSync = exports.evalSnippet = exports.evalSnippetSync = exports.glob = exports.globSync = exports.rewriteSnippetToSyncVersion = exports.rewriteSnippetToAsyncVersion = exports.arrayBody = void 0;
const typescript_1 = __importStar(require("typescript"));
const fs_1 = __importStar(require("fs"));
const child_process_1 = require("child_process");
const path_1 = __importDefault(require("path"));
const fast_glob_1 = __importDefault(require("fast-glob"));
const sync_fetch_1 = __importDefault(require("sync-fetch"));
const url_1 = require("url");
const node_query_1 = __importDefault(require("@synvert-hq/node-query"));
const node_mutation_1 = __importDefault(require("@synvert-hq/node-mutation"));
const errors_1 = require("./errors");
const configuration_1 = __importDefault(require("./configuration"));
const REWRITER_METHODS = "addFile removeFile renameFile withinFiles withinFile addSnippet";
const SCOPE_METHODS = "withinNode withNode findNode gotoNode";
const CONDITION_METHODS = "ifExistNode unlessExistNode ifOnlyExistNode ifAllNodes";
// delete is a reserved word, we define another expression in GLOBAL_DSL_QUERY
const ACTION_METHODS = "group append prepend insert insertAfter insertBefore remove replace replaceWith noop";
const ALL_METHODS = `configure description ifNode ifNpm ${REWRITER_METHODS} ${SCOPE_METHODS} ${CONDITION_METHODS} ${ACTION_METHODS} callHelper wrapWithQuotes appendSemicolon addLeadingSpaces indent`;
const arrayBody = (node, nodeQueryAdapter) => {
switch (nodeQueryAdapter.getNodeType(node)) {
case "SourceFile":
return node["statements"];
case "ClassDeclaration":
return node["members"];
case "ClassDefinition":
return node["body"]["body"];
case "MethodDefinition":
return node["value"]["body"]["body"];
default:
return node["body"];
}
};
exports.arrayBody = arrayBody;
const NEW_REWRITER_WITH_FUNCTION_QUERY = new node_query_1.default(`.NewExpression[expression=Synvert.Rewriter][arguments.length=3][arguments.2=.FunctionExpression[modifiers=undefined]]`, { adapter: "typescript" });
const NEW_HELPER_WITH_FUNCTION_QUERY = new node_query_1.default(`.NewExpression[expression=Synvert.Helper][arguments.length=2][arguments.1=.FunctionExpression[modifiers=undefined]]`, { adapter: "typescript" });
const NEW_INSTANCE_WITH_FUNCTION_QUERY = new node_query_1.default(`.CallExpression[expression=.PropertyAccessExpression[expression=.ThisKeyword]
[name IN (withinFiles withinFile)]][arguments.length=2][arguments.1=.FunctionExpression[modifiers=undefined]]`, { adapter: "typescript" });
const SCOPES_AND_CONDITIONS_QUERY = new node_query_1.default(`.CallExpression[expression=.PropertyAccessExpression[expression=.ThisKeyword]
[name IN (${SCOPE_METHODS} ${CONDITION_METHODS})]]
[arguments.-1.nodeType IN (FunctionExpression ArrowFunction)][arguments.-1.modifiers=undefined]`, { adapter: "typescript" });
const GROUPS_QUERY = new node_query_1.default(`.CallExpression[expression=.PropertyAccessExpression[expression=.ThisKeyword]
[name=group]]
[arguments.-1.nodeType IN (FunctionExpression ArrowFunction)][arguments.-1.modifiers=undefined]`, { adapter: "typescript" });
const ASYNC_METHODS_QUERY = new node_query_1.default(`.CallExpression[expression=.PropertyAccessExpression[expression=.ThisKeyword]
[name IN (${REWRITER_METHODS} callHelper group ${SCOPE_METHODS} ${CONDITION_METHODS})]]`, { adapter: "typescript" });
/**
* Rewrite javascript snippet to async version.
*/
const rewriteSnippetToAsyncVersion = (snippet, insertRequire = true) => {
let newSnippet = addProperScopeToSnippet(snippet);
if (insertRequire) {
newSnippet = insertRequireSynvertCoreToSnippet(newSnippet);
}
const node = parseCode(newSnippet);
const mutation = new node_mutation_1.default(newSnippet, {
adapter: "typescript",
});
NEW_REWRITER_WITH_FUNCTION_QUERY.queryNodes(node).forEach((node) => mutation.insert(node, "async ", { at: "beginning", to: "arguments.-1" }));
NEW_HELPER_WITH_FUNCTION_QUERY.queryNodes(node).forEach((node) => mutation.insert(node, "async ", { at: "beginning", to: "arguments.-1" }));
NEW_INSTANCE_WITH_FUNCTION_QUERY.queryNodes(node).forEach((node) => mutation.insert(node, "async ", { at: "beginning", to: "arguments.-1" }));
SCOPES_AND_CONDITIONS_QUERY.queryNodes(node).forEach((node) => mutation.insert(node, "async ", { at: "beginning", to: "arguments.-1" }));
GROUPS_QUERY.queryNodes(node).forEach((node) => mutation.insert(node, "async ", { at: "beginning", to: "arguments.-1" }));
ASYNC_METHODS_QUERY.queryNodes(node).forEach((node) => {
if (node.parent.kind != typescript_1.SyntaxKind.AwaitExpression) {
mutation.insert(node, "await ", { at: "beginning" });
}
});
const { affected, newSource } = mutation.process();
return affected ? newSource : newSnippet;
};
exports.rewriteSnippetToAsyncVersion = rewriteSnippetToAsyncVersion;
const SYNC_METHODS_QUERY = new node_query_1.default(`.CallExpression[expression=.PropertyAccessExpression[expression=.ThisKeyword]
[name IN (${REWRITER_METHODS} callHelper ${SCOPE_METHODS} ${CONDITION_METHODS})]]`, { adapter: "typescript" });
/**
* Rewrite javascript snippet to sync version.
*/
const rewriteSnippetToSyncVersion = (snippet, insertRequire = true) => {
let newSnippet = addProperScopeToSnippet(snippet);
if (insertRequire) {
newSnippet = insertRequireSynvertCoreToSnippet(newSnippet);
}
const node = parseCode(newSnippet);
const mutation = new node_mutation_1.default(newSnippet, {
adapter: "typescript",
});
SYNC_METHODS_QUERY.queryNodes(node).forEach((node) => mutation.insert(node, "Sync", { at: "end", to: "expression" }));
const { affected, newSource } = mutation.process();
return affected ? newSource : newSnippet;
};
exports.rewriteSnippetToSyncVersion = rewriteSnippetToSyncVersion;
const NOT_HAS_REQUIRE_SYNVERT_CORE_QUERY = new node_query_1.default(`:not_has(.FirstStatement[declarationList=.VariableDeclarationList[declarations.length=1][declarations.0=.VariableDeclaration[name=Synvert][initializer=.CallExpression[expression=require][arguments.length=1][arguments.0=.StringLiteral[text="@synvert-hq/synvert-core"]]]]])`, { adapter: "typescript" });
const insertRequireSynvertCoreToSnippet = (snippet) => {
const node = parseCode(snippet);
const mutation = new node_mutation_1.default(snippet, {
adapter: "typescript",
});
NOT_HAS_REQUIRE_SYNVERT_CORE_QUERY.queryNodes(node).forEach((node) => {
mutation.insert(node, `const Synvert = require("@synvert-hq/synvert-core");\n`, {
at: "beginning",
});
});
const { affected, newSource } = mutation.process();
return affected ? newSource : snippet;
};
const NEW_REWRITER_WITH_ARROW_FUNCTION_QUERY = new node_query_1.default(`.NewExpression[expression=Synvert.Rewriter][arguments.length=3][arguments.2=.ArrowFunction]`, { adapter: "typescript" });
const NEW_HELPER_WITH_ARROW_FUNCTION_QUERY = new node_query_1.default(`.NewExpression[expression=Synvert.Helper][arguments.length=2][arguments.1=.ArrowFunction]`, { adapter: "typescript" });
const NEW_INSTANCE_WITH_ARROW_FUNCTION_QUERY = new node_query_1.default(`.CallExpression[expression IN (withinFiles withinFile)][arguments.length=2][arguments.1=.ArrowFunction]`, { adapter: "typescript" });
const GLOBAL_DSL_QUERY = new node_query_1.default(`.CallExpression[expression IN (${ALL_METHODS})],
.DeleteExpression[expression=.ParenthesizedExpression[expression.nodeType IN (StringLiteral ArrayLiteralExpression)]],
.DeleteExpression[expression=.ParenthesizedExpression[expression=.BinaryExpression[left.nodeType IN (StringLiteral ArrayLiteralExpression)][right.nodeType=ObjectLiteralExpression]]]`, { adapter: "typescript" });
const addProperScopeToSnippet = (snippet) => {
const node = parseCode(snippet);
const mutation = new node_mutation_1.default(snippet, {
adapter: "typescript",
});
NEW_REWRITER_WITH_ARROW_FUNCTION_QUERY.queryNodes(node).forEach((node) => {
mutation.delete(node, "arguments.2.equalsGreaterThanToken");
mutation.insert(node, "function ", { at: "beginning", to: "arguments.2" });
});
NEW_HELPER_WITH_ARROW_FUNCTION_QUERY.queryNodes(node).forEach((node) => {
mutation.delete(node, "arguments.1.equalsGreaterThanToken");
mutation.insert(node, "function ", { at: "beginning", to: "arguments.1" });
});
NEW_INSTANCE_WITH_ARROW_FUNCTION_QUERY.queryNodes(node).forEach((node) => {
mutation.delete(node, "arguments.1.equalsGreaterThanToken");
mutation.insert(node, "function ", { at: "beginning", to: "arguments.1" });
});
GLOBAL_DSL_QUERY.queryNodes(node).forEach((node) => {
mutation.insert(node, "this.", { at: "beginning" });
});
const { affected, newSource } = mutation.process();
return affected ? newSource : snippet;
};
const parseCode = (snippet) => {
return typescript_1.default.createSourceFile("test.js", snippet, typescript_1.default.ScriptTarget.Latest, true, typescript_1.default.ScriptKind.JS);
};
function runShellCommandSync(command, args, input) {
const child = (0, child_process_1.spawnSync)(command, args, {
cwd: configuration_1.default.rootPath,
input: input,
});
let output = child.stdout ? child.stdout.toString() : "";
let error = child.stderr ? child.stderr.toString() : "";
return { stdout: output, stderr: error };
}
/**
* Sync to glob matching files.
* @param {string} filePattern file pattern
* @returns {string[]} matching files
*/
const globSync = (filePattern) => {
const onlyPaths = configuration_1.default.onlyPaths.length > 0 ? configuration_1.default.onlyPaths : [""];
const fsStats = fast_glob_1.default.sync(onlyPaths.map((onlyPath) => path_1.default.join(onlyPath, filePattern)), {
ignore: configuration_1.default.skipPaths,
cwd: configuration_1.default.rootPath,
onlyFiles: true,
unique: true,
stats: true,
});
const allPaths = fsStats
.filter((fsStat) => fsStat.stats.size < configuration_1.default.maxFileSize)
.map((fsStat) => fsStat.path);
if (configuration_1.default.respectGitignore) {
const { stdout } = runShellCommandSync("git", ["check-ignore", "--stdin"], allPaths.join("\n"));
const ignoredPaths = new Set(stdout.split("\n").filter(Boolean));
return allPaths.filter((path) => !ignoredPaths.has(path));
}
return allPaths;
};
exports.globSync = globSync;
function runShellCommand(command, args, input) {
return new Promise((resolve) => {
const child = (0, child_process_1.spawn)(command, args, { cwd: configuration_1.default.rootPath });
if (child.stdin && input) {
child.stdin.write(input);
child.stdin.end();
}
let output = "";
if (child.stdout) {
child.stdout.on("data", (data) => {
output += data;
});
}
let error = "";
if (child.stderr) {
child.stderr.on("data", (data) => {
error += data;
});
}
child.on("error", (e) => {
return resolve({ stdout: "", stderr: e.message });
});
child.on("exit", () => {
return resolve({ stdout: output, stderr: error });
});
});
}
/**
* Async to glob matching files.
* @async
* @param {string} filePattern file pattern
* @returns {Promise<string[]>} matching files
*/
const glob = (filePattern) => __awaiter(void 0, void 0, void 0, function* () {
const onlyPaths = configuration_1.default.onlyPaths.length > 0 ? configuration_1.default.onlyPaths : [""];
const fsStats = yield (0, fast_glob_1.default)(onlyPaths.map((onlyPath) => path_1.default.join(onlyPath, filePattern)), {
ignore: configuration_1.default.skipPaths,
cwd: configuration_1.default.rootPath,
onlyFiles: true,
unique: true,
stats: true,
});
const allPaths = fsStats
.filter((fsStat) => fsStat.stats.size < configuration_1.default.maxFileSize)
.map((fsStat) => fsStat.path);
if (configuration_1.default.respectGitignore) {
const { stdout } = yield runShellCommand("git", ["check-ignore", "--stdin"], allPaths.join("\n"));
const ignoredPaths = new Set(stdout.split("\n").filter(Boolean));
return allPaths.filter((path) => !ignoredPaths.has(path));
}
return allPaths;
});
exports.glob = glob;
/**
* Sync to eval the snippet by name.
* @param {string} snippetName - snippet name, it can be a http url, file path or short snippet name.
* @returns {Rewriter} a Rewriter object
*/
const evalSnippetSync = (snippetName) => {
return eval((0, exports.loadSnippetSync)(snippetName));
};
exports.evalSnippetSync = evalSnippetSync;
/**
* Async to eval the snippet by name.
* @async
* @param {string} snippetName - snippet name, it can be a http url, file path or short snippet name.
* @returns {Promise<Rewriter>} a Rewriter object
*/
const evalSnippet = (snippetName) => __awaiter(void 0, void 0, void 0, function* () {
return eval(yield (0, exports.loadSnippet)(snippetName));
});
exports.evalSnippet = evalSnippet;
/**
* Sync to eval the helper by name.
* @param {string} helperName - helper name, it can be a http url, file path or helper name.
* @returns {Helper} a Helper object
*/
const evalHelperSync = (helperName) => {
return eval((0, exports.loadSnippetSync)(helperName));
};
exports.evalHelperSync = evalHelperSync;
/**
* Async to eval the helper by name.
* @async
* @param {string} helperName - helper name, it can be a http url, file path or helper name.
* @returns {Promise<Helper>} a Helper object
*/
const evalHelper = (helperName) => __awaiter(void 0, void 0, void 0, function* () {
return eval(yield (0, exports.loadSnippet)(helperName));
});
exports.evalHelper = evalHelper;
/**
* Sync to load snippet by snippet name.
* @param {string} snippetName - snippet name, it can be a http url, file path or short snippet name.
* @param {boolean} insertRequire - insert require statement or not
* @returns {string} snippet helper content
* @throws {SnippetNotFoundError} snippet not found
*/
const loadSnippetSync = (snippetName, insertRequire = true) => {
const snippetContent = loadSnippetContentSync(snippetName);
return (0, exports.rewriteSnippetToSyncVersion)(snippetContent, insertRequire);
};
exports.loadSnippetSync = loadSnippetSync;
/**
* Sync to load snippet by snippet name.
* @async
* @param {string} snippetName - snippet name, it can be a http url, file path or short snippet name.
* @param {boolean} insertRequire - insert require statement or not
* @returns {Promise<string>} snippet helper content
* @throws {SnippetNotFoundError} snippet not found
*/
const loadSnippet = (snippetName, insertRequire = true) => __awaiter(void 0, void 0, void 0, function* () {
const snippetContent = yield loadSnippetContent(snippetName);
return (0, exports.rewriteSnippetToAsyncVersion)(snippetContent, insertRequire);
});
exports.loadSnippet = loadSnippet;
/**
* Sync to check if it is a valid file path.
* @param {string} path - file path
* @returns {boolean} gets true it is a valid file
*/
const isValidFileSync = (path) => {
try {
const stats = fs_1.default.statSync(path);
return stats.isFile();
}
catch (_a) {
return false;
}
};
exports.isValidFileSync = isValidFileSync;
/**
* Async to check if it is a valid file path.
* @async
* @param {string} path - file path
* @returns {Promise<boolean>} gets true it is a valid file
*/
const isValidFile = (path) => __awaiter(void 0, void 0, void 0, function* () {
try {
const stats = yield fs_1.promises.stat(path);
return stats.isFile();
}
catch (_a) {
return false;
}
});
exports.isValidFile = isValidFile;
const isValidUrl = (urlString) => {
try {
const url = new url_1.URL(urlString);
return url.protocol === "http:" || url.protocol === "https:";
}
catch (_a) {
return false;
}
};
const formatUrl = (url) => convertToGithubRawUrl(url);
const snippetExpandPath = (snippetName) => path_1.default.join(snippetsHome(), "lib", `${snippetName}.js`);
const remoteSnippetExistsSync = (snippetPath) => (0, sync_fetch_1.default)(snippetPath).status === 200;
const remoteSnippetExists = (snippetPath) => __awaiter(void 0, void 0, void 0, function* () {
const response = yield fetch(snippetPath);
return response.status === 200;
});
const remoteSnippetUrl = (snippetName) => `https://github.com/synvert-hq/synvert-snippets-javascript/blob/main/lib/${snippetName}.js`;
const snippetsHome = () => {
return (process.env.SYNVERT_SNIPPETS_HOME ||
path_1.default.join(process.env.HOME, ".synvert-javascript"));
};
const convertToGithubRawUrl = (url) => {
if (url.startsWith("https://github.com/")) {
return url
.replace("//github.com/", "//raw.githubusercontent.com/")
.replace("/blob/", "/");
}
if (url.startsWith("https://gist.github.com")) {
return (url.replace("gist.github.com/", "gist.githubusercontent.com/") + "/raw");
}
return url;
};
const loadSnippetContent = (snippetName) => __awaiter(void 0, void 0, void 0, function* () {
if (isValidUrl(snippetName)) {
const snippetUrl = formatUrl(snippetName);
if (yield remoteSnippetExists(snippetUrl)) {
const response = yield fetch(snippetUrl);
return yield response.text();
}
throw new errors_1.SnippetNotFoundError(`${snippetName} not found`);
}
else if (yield (0, exports.isValidFile)(snippetName)) {
return yield fs_1.promises.readFile(snippetName, "utf-8");
}
else {
const snippetPath = snippetExpandPath(snippetName);
if (yield (0, exports.isValidFile)(snippetPath)) {
return yield fs_1.promises.readFile(snippetPath, "utf-8");
}
const snippetUrl = formatUrl(remoteSnippetUrl(snippetName));
if (yield remoteSnippetExists(snippetUrl)) {
const response = yield fetch(snippetUrl);
return yield response.text();
}
throw new errors_1.SnippetNotFoundError(`${snippetName} not found`);
}
});
const loadSnippetContentSync = (snippetName) => {
if (isValidUrl(snippetName)) {
const snippetUrl = formatUrl(snippetName);
if (remoteSnippetExistsSync(snippetUrl)) {
return (0, sync_fetch_1.default)(snippetUrl).text();
}
throw new errors_1.SnippetNotFoundError(`${snippetName} not found`);
}
else if ((0, exports.isValidFileSync)(snippetName)) {
return fs_1.default.readFileSync(snippetName, "utf-8");
}
else {
const snippetPath = snippetExpandPath(snippetName);
if ((0, exports.isValidFileSync)(snippetPath)) {
return fs_1.default.readFileSync(snippetPath, "utf-8");
}
const snippetUrl = formatUrl(remoteSnippetUrl(snippetName));
if (remoteSnippetExistsSync(snippetUrl)) {
return (0, sync_fetch_1.default)(snippetUrl).text();
}
throw new errors_1.SnippetNotFoundError(`${snippetName} not found`);
}
};