scraipt
Version:
Scrape away inefficient code during compile-time using AI
184 lines (183 loc) • 8.41 kB
JavaScript
;
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 });
// Setup dotenv before anything else to ensure environment variables are available
const dotenv_1 = require("dotenv");
(0, dotenv_1.config)();
const core_1 = require("@babel/core");
const generator_1 = __importDefault(require("@babel/generator"));
const OpenAI_1 = require("./modules/OpenAI/OpenAI");
const codeProcessing_1 = require("./modules/codeProcessing");
const fileUtils_1 = require("./modules/fileUtils");
const cache_1 = require("./modules/cache");
const chalk_1 = __importDefault(require("chalk"));
const openai = new OpenAI_1.OpenAIAPI();
const cache = new cache_1.Cache();
/**
If the line above the node contains a comment with the text "scraipt-pass", the node should be left as is.
* @param node The node to check.
* @param source The source code.
* @returns True if the node should be left as is, false otherwise.
*/
const shouldPassNode = (node, source) => {
var _a;
if ((_a = !node) !== null && _a !== void 0 ? _a : !node.loc) {
return false;
}
const previousLine = (0, codeProcessing_1.getSourceAtLine)(source, node.loc.start.line - 1);
if (!previousLine) {
return false;
}
return /(\/\*)|(\/\/)*(.*?scraipt-pass)/.test(previousLine);
};
/**
Create a source context by removing the node source from the source code.
* @param source The source code.
* @param nodeSource The source code of the node.
* @returns The source context.
*/
const createSourceContext = (source, nodeSource) => {
// TODO: This is a bit janky, we should use the AST to remove the node instead of using string manipulation
return source.replace(nodeSource, '');
};
/**
Transform the given source code to be more efficient and optimized using all available resources.
* @param source The source code to transform.
* @param path The path of the source code. If not provided, the source code will not be written to a file.
* @returns The transformed source code.
*/
const transformSourceCode = (source, options, resourcePath) => __awaiter(void 0, void 0, void 0, function* () {
var _a;
if (!source || !options || !resourcePath) {
return source;
}
// Setup OpenAI parameters
const model = (_a = options.model) !== null && _a !== void 0 ? _a : 'gpt-4';
openai.maxTokenCount = options.maxTokens;
// Only include files listed in the include option
if (options.include) {
for (const include of options.include) {
if (!resourcePath.includes(include)) {
return source;
}
// TODO: minimatch for glob patterns
// if (!minimatch(resourcePath, include)) {
// return source;
// }
}
}
const AST = (0, codeProcessing_1.parseSource)(source);
if (!AST) {
return source;
}
const workers = [];
let transformedSource = source;
(0, core_1.traverse)(AST, {
// No need for asynchroneous execution as we can just run the code synchronously and wait for the result since we aren't modifying the AST
enter(path) {
const worker = () => __awaiter(this, void 0, void 0, function* () {
var _a, _b, _c, _d;
const isArrowFunction = path.isArrowFunctionExpression();
// If the node is not a function, skip it
if (!path.isFunctionDeclaration() && !isArrowFunction)
return;
const node = path.node;
if ((_c = (_b = (_a = !node) !== null && _a !== void 0 ? _a : !node.start) !== null && _b !== void 0 ? _b : !node.end) !== null && _c !== void 0 ? _c : !node.loc) {
return;
}
// Get the actual source code of the node
const nodeSource = source.substring(node.start, node.end);
if (shouldPassNode(node, source)) {
return;
}
// Check for the node in the cache
const cachedNode = cache.get(nodeSource);
if (cachedNode) {
transformedSource = source.replace(nodeSource, cachedNode);
return;
}
// Source with the node removed
const sourceContext = createSourceContext
? createSourceContext(source, nodeSource)
: source;
if (options.dryRun) {
return;
}
// Generate the optimized version of the code
const completion = yield openai.createTextCompletion(sourceContext, nodeSource, model);
if (!completion) {
return;
}
const completionAST = (0, codeProcessing_1.parseSource)(completion);
if (!completionAST) {
return;
}
const completionFunctionAST = completionAST
.program.body[0];
if (!completionFunctionAST) {
return;
}
const completionBodyAST = completionFunctionAST.body;
if (!completionBodyAST) {
return;
}
const completionBody = (0, generator_1.default)(completionBodyAST).code;
if (!completionBody) {
return;
}
const originalNodeBodyAST = node.body;
const originalNodeBody = (0, generator_1.default)(originalNodeBodyAST).code;
// Get the source code of the function decleration
const nodeFunctionDeclerationLine = (0, codeProcessing_1.getSourceAtLine)(source, node.loc.start.line);
// A litle janky but it works
const originalParams = nodeFunctionDeclerationLine
.split('(')[1]
.split(')')[0];
let nodeName;
if (!isArrowFunction) {
nodeName = (_d = node.id) === null || _d === void 0 ? void 0 : _d.name;
}
const transformedNodeSource = (0, codeProcessing_1.createFunction)(nodeName, originalParams, completionBody, originalNodeBody, isArrowFunction);
// Replace the original source code with the optimized version
transformedSource = source.replace(nodeSource, transformedNodeSource);
// Add to the cache
cache.set(nodeSource, transformedNodeSource);
});
workers.push(worker);
},
});
yield Promise.all(workers.map((worker) => worker()));
// Check if the transformed code is valid javascript
if (!(0, codeProcessing_1.isValidJavaScript)(transformedSource)) {
return source;
}
if (options.debug) {
(0, fileUtils_1.writeScraiptFile)(resourcePath, transformedSource, options.buildPath);
}
if (options.dryRun) {
console.log(`${chalk_1.default.blue('Scraipt')} Would have wrote: ${resourcePath}`);
return source;
}
return transformedSource;
});
// Main webpack loader function
module.exports = function (source) {
// Not sure why but "this" needs to be casted to "any" to avoid type errors
const options = this.getOptions();
const resourcePath = this.resourcePath;
if (options.debug) {
(0, fileUtils_1.createScraiptFolder)(options.buildPath);
}
return transformSourceCode(source, options, resourcePath);
};