diff --git a/src/constants.js b/src/constants.js new file mode 100644 index 0000000..a142445 --- /dev/null +++ b/src/constants.js @@ -0,0 +1,73 @@ +"use strict"; +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 __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (g && (g = 0, op[0] && (_ = 0)), _) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.assignLineNumbers = exports.getParserForExtension = exports.processGitFilepath = exports.sleep = void 0; +var javascript_parser_1 = require("./context/language/javascript-parser"); +var sleep = function (ms) { return __awaiter(void 0, void 0, void 0, function () { + return __generator(this, function (_a) { + return [2 /*return*/, new Promise(function (resolve) { return setTimeout(resolve, ms); })]; + }); +}); }; +exports.sleep = sleep; +var processGitFilepath = function (filepath) { + // Remove the leading '/' if it exists + return filepath.startsWith("/") ? filepath.slice(1) : filepath; +}; +exports.processGitFilepath = processGitFilepath; +var EXTENSIONS_TO_PARSERS = new Map([ + ["ts", new javascript_parser_1.JavascriptParser()], + ["tsx", new javascript_parser_1.JavascriptParser()], + ["js", new javascript_parser_1.JavascriptParser()], + ["jsx", new javascript_parser_1.JavascriptParser()], +]); +var getParserForExtension = function (filename) { + var fileExtension = filename.split(".").pop().toLowerCase(); + return EXTENSIONS_TO_PARSERS.get(fileExtension) || null; +}; +exports.getParserForExtension = getParserForExtension; +var assignLineNumbers = function (contents) { + var lines = contents.split("\n"); + var lineNumber = 1; + var linesWithNumbers = lines.map(function (line) { + var numberedLine = "".concat(lineNumber, ": ").concat(line); + lineNumber++; + return numberedLine; + }); + return linesWithNumbers.join("\n"); +}; +exports.assignLineNumbers = assignLineNumbers; diff --git a/src/constants.ts b/src/constants.ts index 14c7de1..8c48b74 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -89,8 +89,11 @@ export const processGitFilepath = (filepath: string) => { return filepath.startsWith("/") ? filepath.slice(1) : filepath; }; +// Updated EnclosingContext interface export interface EnclosingContext { - enclosingContext: Node | null; + enclosingContext: Node | null; // Existing property + type: "function" | "class"; // Added property + name: string; // Added property } export interface AbstractParser { diff --git a/src/context/language/javascript-parser.js b/src/context/language/javascript-parser.js new file mode 100644 index 0000000..b609326 --- /dev/null +++ b/src/context/language/javascript-parser.js @@ -0,0 +1,61 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.JavascriptParser = void 0; +var parser = require("@babel/parser"); +var traverse_1 = require("@babel/traverse"); +var processNode = function (path, lineStart, lineEnd, largestSize, largestEnclosingContext) { + var _a = path.node.loc, start = _a.start, end = _a.end; + if (start.line <= lineStart && lineEnd <= end.line) { + var size = end.line - start.line; + if (size > largestSize) { + largestSize = size; + largestEnclosingContext = path.node; + } + } + return { largestSize: largestSize, largestEnclosingContext: largestEnclosingContext }; +}; +var JavascriptParser = /** @class */ (function () { + function JavascriptParser() { + } + JavascriptParser.prototype.findEnclosingContext = function (file, lineStart, lineEnd) { + var ast = parser.parse(file, { + sourceType: "module", + plugins: ["jsx", "typescript"], // To allow JSX and TypeScript + }); + var largestEnclosingContext = null; + var largestSize = 0; + (0, traverse_1.default)(ast, { + Function: function (path) { + var _a; + (_a = processNode(path, lineStart, lineEnd, largestSize, largestEnclosingContext), largestSize = _a.largestSize, largestEnclosingContext = _a.largestEnclosingContext); + }, + TSInterfaceDeclaration: function (path) { + var _a; + (_a = processNode(path, lineStart, lineEnd, largestSize, largestEnclosingContext), largestSize = _a.largestSize, largestEnclosingContext = _a.largestEnclosingContext); + }, + }); + return { + enclosingContext: largestEnclosingContext, + }; + }; + JavascriptParser.prototype.dryRun = function (file) { + try { + var ast = parser.parse(file, { + sourceType: "module", + plugins: ["jsx", "typescript"], // To allow JSX and TypeScript + }); + return { + valid: true, + error: "", + }; + } + catch (exc) { + return { + valid: false, + error: exc, + }; + } + }; + return JavascriptParser; +}()); +exports.JavascriptParser = JavascriptParser; diff --git a/src/context/language/python-parser.js b/src/context/language/python-parser.js new file mode 100644 index 0000000..0dee0f5 --- /dev/null +++ b/src/context/language/python-parser.js @@ -0,0 +1,62 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.PythonParser = void 0; +var PythonParser = /** @class */ (function () { + function PythonParser() { + } + /** + * Finds the enclosing context (function or class) of a given line range. + * @param file - The Python file content as a string. + * @param lineStart - The starting line number. + * @param lineEnd - The ending line number. + * @returns The enclosing context, or null if not found. + */ + PythonParser.prototype.findEnclosingContext = function (file, lineStart, lineEnd) { + var lines = file.split("\n"); + var context = null; + // Search upwards from the starting line to find the enclosing context + for (var i = lineStart - 1; i >= 0; i--) { + var line = lines[i].trim(); + if (line.startsWith("def ")) { + context = { + type: "function", + name: line.split(" ")[1].split("(")[0], // Extract function name + }; + break; + } + else if (line.startsWith("class ")) { + context = { + type: "class", + name: line.split(" ")[1].split("(")[0], // Extract class name + }; + break; + } + } + return context; // Returns null if no context is found + }; + /** + * Performs a dry run to check if the Python file is valid syntax. + * @param file - The Python file content as a string. + * @returns An object indicating validity and any error message. + */ + PythonParser.prototype.dryRun = function (file) { + try { + var spawnSync = require("child_process").spawnSync; + var result = spawnSync("python3", ["-m", "py_compile", "-"], { + input: file, + encoding: "utf-8", + }); + if (result.status === 0) { + return { valid: true, error: "" }; // File is valid + } + else { + return { valid: false, error: result.stderr.toString() }; // Syntax error + } + } + catch (e) { + return { valid: false, error: e.message }; // Handle unexpected errors + } + }; + return PythonParser; +}()); +exports.PythonParser = PythonParser; diff --git a/src/context/language/python-parser.ts b/src/context/language/python-parser.ts index 845e90b..5f7545b 100644 --- a/src/context/language/python-parser.ts +++ b/src/context/language/python-parser.ts @@ -1,15 +1,62 @@ import { AbstractParser, EnclosingContext } from "../../constants"; + export class PythonParser implements AbstractParser { + /** + * Finds the enclosing context (function or class) of a given line range. + * @param file - The Python file content as a string. + * @param lineStart - The starting line number. + * @param lineEnd - The ending line number. + * @returns The enclosing context, or null if not found. + */ findEnclosingContext( file: string, lineStart: number, lineEnd: number ): EnclosingContext { - // TODO: Implement this method for Python - return null; + const lines = file.split("\n"); + let context: EnclosingContext = null; + + // Search upwards from the starting line to find the enclosing context + for (let i = lineStart - 1; i >= 0; i--) { + const line = lines[i].trim(); + if (line.startsWith("def ")) { + context = { + type: "function", + name: line.split(" ")[1].split("(")[0], // Extract function name + }; + break; + } else if (line.startsWith("class ")) { + context = { + type: "class", + name: line.split(" ")[1].split("(")[0], // Extract class name + }; + break; + } + } + + return context; // Returns null if no context is found } + + /** + * Performs a dry run to check if the Python file is valid syntax. + * @param file - The Python file content as a string. + * @returns An object indicating validity and any error message. + */ dryRun(file: string): { valid: boolean; error: string } { - // TODO: Implement this method for Python - return { valid: false, error: "Not implemented yet" }; + try { + const { spawnSync } = require("child_process"); + const result = spawnSync("python3", ["-m", "py_compile", "-"], { + input: file, // Pass the file content to Python + encoding: "utf-8", + }); + + if (result.status === 0) { + return { valid: true, error: "" }; // File is valid + } else { + return { valid: false, error: result.stderr.toString() }; // Syntax error + } + } catch (e) { + return { valid: false, error: e.message }; // Handle unexpected errors + } } }