diff --git a/.vscode/launch.json b/.vscode/launch.json index 8880465..4e94752 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -18,4 +18,4 @@ "preLaunchTask": "${defaultBuildTask}" } ] -} +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 2a8dbd1..2b49ae0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "keployio", - "version": "1.0.7", + "version": "1.0.6", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "keployio", - "version": "1.0.7", + "version": "1.0.6", "dependencies": { "@sentry/node": "^8.28.0", "acorn-walk": "^8.3.3", @@ -14,6 +14,11 @@ "js-yaml": "^4.1.0", "sinon": "^17.0.1", "svelte": "^4.2.12", + "tree-sitter": "^0.21.1", + "tree-sitter-go": "^0.23.0", + "tree-sitter-java": "^0.21.0", + "tree-sitter-javascript": "^0.21.4", + "tree-sitter-python": "^0.21.0", "uuid": "^10.0.0", "walk": "^2.3.15", "yaml": "^2.4.2" @@ -3571,6 +3576,26 @@ "path-to-regexp": "^6.2.1" } }, + "node_modules/node-addon-api": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.1.0.tgz", + "integrity": "sha512-yBY+qqWSv3dWKGODD6OGE6GnTX7Q2r+4+DfpqxHSHh8x0B4EKP9+wVGLS6U/AM1vxSNNmUEuIV5EGhYwPpfOwQ==", + "license": "MIT", + "engines": { + "node": "^18 || ^20 || >= 21" + } + }, + "node_modules/node-gyp-build": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.1.tgz", + "integrity": "sha512-OSs33Z9yWr148JZcbZd5WiAXhh/n9z8TxQcdMhIOlpN9AhWpLfvVFO73+m77bBABQMaY9XSvIa+qk0jlI7Gcaw==", + "license": "MIT", + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -4804,6 +4829,98 @@ "tree-kill": "cli.js" } }, + "node_modules/tree-sitter": { + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/tree-sitter/-/tree-sitter-0.21.1.tgz", + "integrity": "sha512-7dxoA6kYvtgWw80265MyqJlkRl4yawIjO7S5MigytjELkX43fV2WsAXzsNfO7sBpPPCF5Gp0+XzHk0DwLCq3xQ==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "node-addon-api": "^8.0.0", + "node-gyp-build": "^4.8.0" + } + }, + "node_modules/tree-sitter-go": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/tree-sitter-go/-/tree-sitter-go-0.23.0.tgz", + "integrity": "sha512-6wycuKQCTRz/8OTlBL7xvUZP9aeznxKbWh5xYD1u0WXTCAwQM/xUQr92SKwcZexn9IIMczbzfjGgm6e5iFvSBA==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "node-addon-api": "^8.1.0", + "node-gyp-build": "^4.8.1" + }, + "peerDependencies": { + "tree-sitter": "^0.21.0" + }, + "peerDependenciesMeta": { + "tree_sitter": { + "optional": true + } + } + }, + "node_modules/tree-sitter-java": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/tree-sitter-java/-/tree-sitter-java-0.21.0.tgz", + "integrity": "sha512-CKJiTo1uc3SUsgEcaZgufGx8my6dzihy8JR/JsJH40Tj3uSe2/eFLk+0q+fpbosGAyY4YiXJtEoFB2O4bS2yOw==", + "hasInstallScript": true, + "dependencies": { + "node-addon-api": "^8.0.0", + "node-gyp-build": "^4.8.0" + }, + "peerDependencies": { + "tree-sitter": "^0.21.0" + }, + "peerDependenciesMeta": { + "tree_sitter": { + "optional": true + } + } + }, + "node_modules/tree-sitter-javascript": { + "version": "0.21.4", + "resolved": "https://registry.npmjs.org/tree-sitter-javascript/-/tree-sitter-javascript-0.21.4.tgz", + "integrity": "sha512-Lrk8yahebwrwc1sWJE9xPcz1OnnqiEV7Dh5fbN6EN3wNAdu9r06HpTqLqDwUUbnG4EB46Sfk+FJFAOldfoKLOw==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "node-addon-api": "^8.0.0", + "node-gyp-build": "^4.8.1" + }, + "peerDependencies": { + "tree-sitter": "^0.21.1" + }, + "peerDependenciesMeta": { + "tree_sitter": { + "optional": true + } + } + }, + "node_modules/tree-sitter-python": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/tree-sitter-python/-/tree-sitter-python-0.21.0.tgz", + "integrity": "sha512-IUKx7JcTVbByUx1iHGFS/QsIjx7pqwTMHL9bl/NGyhyyydbfNrpruo2C7W6V4KZrbkkCOlX8QVrCoGOFW5qecg==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "node-addon-api": "^7.1.0", + "node-gyp-build": "^4.8.0" + }, + "peerDependencies": { + "tree-sitter": "^0.21.0" + }, + "peerDependenciesMeta": { + "tree_sitter": { + "optional": true + } + } + }, + "node_modules/tree-sitter-python/node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "license": "MIT" + }, "node_modules/ts-api-utils": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", @@ -7648,6 +7765,16 @@ "path-to-regexp": "^6.2.1" } }, + "node-addon-api": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.1.0.tgz", + "integrity": "sha512-yBY+qqWSv3dWKGODD6OGE6GnTX7Q2r+4+DfpqxHSHh8x0B4EKP9+wVGLS6U/AM1vxSNNmUEuIV5EGhYwPpfOwQ==" + }, + "node-gyp-build": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.1.tgz", + "integrity": "sha512-OSs33Z9yWr148JZcbZd5WiAXhh/n9z8TxQcdMhIOlpN9AhWpLfvVFO73+m77bBABQMaY9XSvIa+qk0jlI7Gcaw==" + }, "normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -8536,6 +8663,58 @@ "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", "dev": true }, + "tree-sitter": { + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/tree-sitter/-/tree-sitter-0.21.1.tgz", + "integrity": "sha512-7dxoA6kYvtgWw80265MyqJlkRl4yawIjO7S5MigytjELkX43fV2WsAXzsNfO7sBpPPCF5Gp0+XzHk0DwLCq3xQ==", + "requires": { + "node-addon-api": "^8.0.0", + "node-gyp-build": "^4.8.0" + } + }, + "tree-sitter-go": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/tree-sitter-go/-/tree-sitter-go-0.23.0.tgz", + "integrity": "sha512-6wycuKQCTRz/8OTlBL7xvUZP9aeznxKbWh5xYD1u0WXTCAwQM/xUQr92SKwcZexn9IIMczbzfjGgm6e5iFvSBA==", + "requires": { + "node-addon-api": "^8.1.0", + "node-gyp-build": "^4.8.1" + } + }, + "tree-sitter-java": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/tree-sitter-java/-/tree-sitter-java-0.21.0.tgz", + "integrity": "sha512-CKJiTo1uc3SUsgEcaZgufGx8my6dzihy8JR/JsJH40Tj3uSe2/eFLk+0q+fpbosGAyY4YiXJtEoFB2O4bS2yOw==", + "requires": { + "node-addon-api": "^8.0.0", + "node-gyp-build": "^4.8.0" + } + }, + "tree-sitter-javascript": { + "version": "0.21.4", + "resolved": "https://registry.npmjs.org/tree-sitter-javascript/-/tree-sitter-javascript-0.21.4.tgz", + "integrity": "sha512-Lrk8yahebwrwc1sWJE9xPcz1OnnqiEV7Dh5fbN6EN3wNAdu9r06HpTqLqDwUUbnG4EB46Sfk+FJFAOldfoKLOw==", + "requires": { + "node-addon-api": "^8.0.0", + "node-gyp-build": "^4.8.1" + } + }, + "tree-sitter-python": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/tree-sitter-python/-/tree-sitter-python-0.21.0.tgz", + "integrity": "sha512-IUKx7JcTVbByUx1iHGFS/QsIjx7pqwTMHL9bl/NGyhyyydbfNrpruo2C7W6V4KZrbkkCOlX8QVrCoGOFW5qecg==", + "requires": { + "node-addon-api": "^7.1.0", + "node-gyp-build": "^4.8.0" + }, + "dependencies": { + "node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==" + } + } + }, "ts-api-utils": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", diff --git a/package.json b/package.json index 63d442b..7727ba2 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "keployio", "displayName": "Keploy", "description": "Streamline testing with the power of Keploy, directly in your favorite IDE.", - "version": "1.0.7", + "version": "1.0.6", "publisher": "Keploy", "icon": "media/logo.png", "pricing": "Free", @@ -21,7 +21,8 @@ ], "activationEvents": [ "onCommand:keploy.utg", - "onLanguage:javascript" + "onLanguage:javascript", + "onUri:keploy.handleUri" ], "main": "./out/extension.js", "contributes": { @@ -81,6 +82,11 @@ "group": "navigation", "when": "keploy.signedIn != true && view == Keploy-Sidebar" }, + { + "command": "keploy.SignInWithOthers", + "group": "navigation", + "when": "keploy.signedIn != true && view == Keploy-Sidebar" + }, { "command": "keploy.SignOut", "group": "navigation", @@ -94,6 +100,11 @@ "title": "Sign In", "when": "keploy.signedIn != true && view == Keploy-Sidebar" }, + { + "command": "keploy.SignInWithOthers", + "title": "Sign In With Others", + "when": "keploy.signedIn != true && view == Keploy-Sidebar" + }, { "command": "keploy.SignOut", "title": "Sign Out", @@ -205,6 +216,11 @@ "js-yaml": "^4.1.0", "sinon": "^17.0.1", "svelte": "^4.2.12", + "tree-sitter": "^0.21.1", + "tree-sitter-java": "^0.21.0", + "tree-sitter-go": "^0.23.0", + "tree-sitter-javascript": "^0.21.4", + "tree-sitter-python": "^0.21.0", "uuid": "^10.0.0", "walk": "^2.3.15", "yaml": "^2.4.2" diff --git a/scripts/utg.sh b/scripts/utg.sh index f3c49ef..edd3638 100644 --- a/scripts/utg.sh +++ b/scripts/utg.sh @@ -5,18 +5,46 @@ export API_KEY="1234" sourceFilePath=$1 testFilePath=$2 +coverageReportPath=$3 +command=$4 -# Add env variables to the npm test command -# utgEnv=" -- --coverage --coverageReporters=text --coverageReporters=cobertura --coverageDirectory=./coverage" +# Get the file extension +extension="${sourceFilePath##*.}" -# testCommand="npm test "+ $utgEnv +# If the file is a Python file, install pytest-cov +if [ "$extension" = "py" ]; then + echo "Installing pytest-cov..." + pip3 install pytest-cov --break-system-packages +fi +if [ "$extension" = "go" ]; then + echo "Installing Go coverage tools..." + go install github.com/axw/gocov/gocov@v1.1.0 + go install github.com/AlekSi/gocov-xml@v1.1.0 + export PATH=$PATH:$(go env GOPATH)/bin +fi -keploy gen --source-file-path="$sourceFilePath" \ - --test-file-path="$testFilePath" \ - --test-command="npm test -- --coverage --coverageReporters=text --coverageReporters=cobertura --coverageDirectory=./coverage -" \ - --coverage-report-path="./coverage/cobertura-coverage.xml"\ - --llmApiVersion "2024-02-01" --llmBaseUrl "https://api.keploy.io" --max-iterations "10" +# Add env variables to the npm test command +# utgEnv=" -- --coverage --coverageReporters=text --coverageReporters=cobertura --coverageDirectory=./coverage" +# Construct the keploy gen command +if [ "$extension" = "java" ]; then + keploy gen --source-file-path="$sourceFilePath" \ + --test-file-path="$testFilePath" \ + --test-command="$command" \ + --coverage-report-path="$coverageReportPath" \ + --llmApiVersion "2024-02-01" \ + --llmBaseUrl "https://api.keploy.io" \ + --max-iterations "10" \ + --coverageFormat jacoco +else + keploy gen --source-file-path="$sourceFilePath" \ + --test-file-path="$testFilePath" \ + --test-command="$command" \ + --coverage-report-path="$coverageReportPath" \ + --llmApiVersion "2024-02-01" \ + --llmBaseUrl "https://api.keploy.io" \ + --max-iterations "10" \ + --coverageFormat cobertura +fi \ No newline at end of file diff --git a/src/OneClickInstall.ts b/src/OneClickInstall.ts index 89e1469..23265a0 100644 --- a/src/OneClickInstall.ts +++ b/src/OneClickInstall.ts @@ -3,7 +3,7 @@ import { exec } from 'child_process'; export default function executeKeployOneClickCommand(): void { // check before if keploy has been installed first const checkKeployExistsCommand = `keploy`; - const installationCommand = `curl--silent - O - L https://keploy.io/install.sh && bash install.sh`; + const installationCommand = `curl --silent -O -L https://keploy.io/install.sh && bash install.sh`; exec(checkKeployExistsCommand, (error, stdout, stderr) => { if (error) { diff --git a/src/SignIn.ts b/src/SignIn.ts index dd432dd..bf33024 100644 --- a/src/SignIn.ts +++ b/src/SignIn.ts @@ -1,5 +1,6 @@ import * as vscode from 'vscode'; import * as http from 'http'; +import * as fs from 'fs' import { v4 as uuidv4 } from 'uuid'; const os = require('os'); const { execSync } = require('child_process'); @@ -65,6 +66,10 @@ export async function getMicrosoftAccessToken() { } } +function generateRandomState() { + return [...Array(30)].map(() => Math.random().toString(36)[2]).join(''); +} + export default async function SignInWithGitHub() { @@ -109,10 +114,70 @@ export default async function SignInWithGitHub() { res.end('

State mismatch. Authentication failed.

'); } server.close(); - } + } }).listen(3000); // Change the port if needed } + +export async function SignInWithOthers() { + const state = generateRandomState(); // Generate a secure random state + const authUrl = `http://localhost:3000/signin?vscode=true&state=${state}`; + vscode.env.openExternal(vscode.Uri.parse(authUrl)); + + return new Promise((resolve, reject) => { + const server = http.createServer(async (req, res) => { + res.setHeader('Access-Control-Allow-Origin', '*'); + res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS'); + res.setHeader('Access-Control-Allow-Headers', 'Content-Type'); + if (req.method === 'OPTIONS') { + res.writeHead(200); + res.end(); + return; + } + + if (req && req.url && req.url.startsWith('/login/keploy/callback')) { + const url = new URL(req.url, `http://${req.headers.host}`); + const receivedState = url.searchParams.get('state'); + const token = url.searchParams.get('token'); + console.log("Received state:", receivedState); + console.log("Received token:", token); + + if (!receivedState || !token) { + res.writeHead(400, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ error: 'Missing state or token' })); + reject(new Error('Missing state or token')); + server.close(); + return; + } + + try { + // Simulate processing the token + console.log("Processing token..."); + + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ message: 'Token received and processed', token, receivedState })); + + // Resolve the promise with the token + resolve(token.toString()); + } catch (err) { + console.error('Error processing token:', err); + res.writeHead(500, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ error: 'Internal Server Error' })); + reject(err); + } finally { + server.close(); // Close the server once the request is handled + } + } else { + res.writeHead(404, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ error: 'Not Found' })); + } + }).listen(3001, () => { + console.log('Server listening on port 3001'); + }); + }); +} + + // async function fetchAccessToken(code: string | null) { // // Exchange the authorization code for an access token // const response = await fetch('https://app.keploy.io/token', { @@ -163,17 +228,56 @@ export async function loginAPI(url = "", provider = "", code = "") { export async function getInstallationID(): Promise { let id; + + const dbusPath = "/var/lib/dbus/machine-id"; + // dbusPathEtc is the default path for dbus machine id located in /etc. + // Some systems (like Fedora 20) only know this path. + // Sometimes it's the other way round. + const dbusPathEtc = "/etc/machine-id"; + + // Reads the content of a file and returns it as a string. + // If the file cannot be read, it throws an error. + function readFile(filePath: string): string { + try { + return fs.readFileSync(filePath, 'utf-8').trim(); + } catch (err) { + throw new Error(`Error reading file ${filePath}: ${err}`); + } + } + + function machineID(): string { + let id = ""; + try { + id = readFile(dbusPath); + } catch (err) { + // Try the fallback path + try { + id = readFile(dbusPathEtc); + } catch (err) { + console.error("Failed to read machine ID from both paths:", err); + throw new Error("Failed to get machine ID"); + } + } + return id; + } try { const inDocker = process.env.IN_DOCKER === 'true'; if (inDocker) { id = process.env.INSTALLATION_ID; } else { - // Run the macOS specific command to get the IOPlatformUUID - const output = execSync('ioreg -rd1 -c IOPlatformExpertDevice').toString(); - id = extractID(output); + const platform = os.platform(); + if (platform === 'darwin') { + // macOS specific command to get the IOPlatformUUID + const output = execSync('ioreg -rd1 -c IOPlatformExpertDevice').toString(); + id = extractID(output); + } else if (platform === 'linux') { + // Use the new machineID function for Linux + id = machineID(); + } else { + throw new Error(`Unsupported platform: ${platform}`); + } } - if (!id) { console.error("Got empty machine id"); throw new Error("Empty machine id"); @@ -223,7 +327,7 @@ export async function validateFirst(token: string, serverURL: string): Promise<{ GitHubToken: token, InstallationID: installationID, }; - + console.log("Request Body:", requestBody); try { const response: AxiosResponse = await axios.post(url, requestBody, { headers: { 'Content-Type': 'application/json' }, diff --git a/src/Utg.ts b/src/Utg.ts index 3e957f9..6f65312 100644 --- a/src/Utg.ts +++ b/src/Utg.ts @@ -4,57 +4,96 @@ import * as path from 'path'; async function Utg(context: vscode.ExtensionContext) { + try { return new Promise((resolve, reject) => { try { - // Create a terminal named "Keploy Terminal" const terminal = vscode.window.createTerminal("Keploy Terminal"); - terminal.show(); - // Your command logic here + const editor = vscode.window.activeTextEditor; let currentFilePath = ""; if (editor) { const document = editor.document; currentFilePath = document.uri.fsPath; vscode.window.showInformationMessage(`Current opened file: ${currentFilePath}`); - // Add your additional logic here } else { vscode.window.showInformationMessage('No file is currently opened.'); + return; } - const scriptPath = path.join(context.extensionPath, 'scripts', 'utg.sh'); - + const scriptPath = path.join(context.extensionPath, 'scripts', 'utg.sh'); const sourceFilePath = currentFilePath; ensureTestFileExists(sourceFilePath); - if (!vscode.workspace.workspaceFolders) { vscode.window.showErrorMessage('No workspace is opened.'); return; } - const rootDir = vscode.workspace.workspaceFolders[0].uri.fsPath; // Root directory of the project - const testDir = path.join(rootDir, 'test'); - const testFilePath = path.join(testDir, path.basename(sourceFilePath).replace('.js', '.test.js')); - if (!fs.existsSync(testFilePath)) { - vscode.window.showInformationMessage("Test doesn't exists", testFilePath); - fs.writeFileSync(testFilePath, `// Test file for ${testFilePath}`); + + const rootDir = vscode.workspace.workspaceFolders[0].uri.fsPath; + const extension = path.extname(sourceFilePath); + let testFilePath: string; + let command: string; + let coverageReportPath: string; + let testFileContent:string; + + if (extension === '.js' || extension === '.ts') { + testFilePath = path.join(path.join(rootDir, 'test'), path.basename(sourceFilePath).replace(extension, `.test${extension}`)); + if (!fs.existsSync(testFilePath)) { + vscode.window.showInformationMessage("Test doesn't exist", testFilePath); + fs.writeFileSync(testFilePath, `// Test file for ${testFilePath}`); + } + command = `npm test -- --coverage --coverageReporters=text --coverageReporters=cobertura --coverageDirectory=./coverage`; + coverageReportPath = "./coverage/cobertura-coverage.xml"; + + } else if (extension === '.py') { + const testDir = path.join(rootDir,'test'); + testFilePath = path.join(rootDir,'test_'+ path.basename(sourceFilePath)); + if (!fs.existsSync(testFilePath)) { + vscode.window.showInformationMessage("Test doesn't exist", testFilePath); + fs.writeFileSync(testFilePath, `// Test file for ${testFilePath}`); + } + command = `pytest --cov=${path.basename(sourceFilePath,'.py')} --cov-report=xml:coverage.xml ${testFilePath}`; + coverageReportPath = "./coverage.xml"; + + }else if (extension === '.java') { + const testDir = path.join(rootDir, 'src', 'test', 'java'); + const testFileName = path.basename(sourceFilePath).replace('.java', 'Test.java'); + testFilePath = path.join(testDir, testFileName); + + if (!fs.existsSync(testFilePath)) { + vscode.window.showInformationMessage("Test doesn't exist", testFilePath); + fs.writeFileSync(testFilePath, `// Test file for ${testFilePath}`); + } + command = `mvn clean test jacoco:report`; + coverageReportPath = "./target/site/jacoco/jacoco.xml"; + } else if (extension === '.go') { + const testDir = path.join(rootDir, 'test'); + testFilePath = path.join(testDir, path.basename(sourceFilePath).replace('.go', '_test.go')); + + if (!fs.existsSync(testFilePath)) { + vscode.window.showInformationMessage("Test doesn't exist", testFilePath); + const uniqueFuncName = path.basename(sourceFilePath).replace('.go', 'Test') + testFileContent = `package main\n\nimport "testing"`; + fs.writeFileSync(testFilePath, testFileContent); } + command = `go test -v ./... -coverprofile=coverage.out && gocov convert coverage.out | gocov-xml > coverage.xml`; + coverageReportPath = "./coverage.xml"; + } + else { + vscode.window.showErrorMessage(`Unsupported file type: ${extension}`); + return; } - vscode.window.showInformationMessage("testFilePath", testFilePath); - const coverageReportPath = "./coverage/cobertura-coverage.xml"; - terminal.sendText(`sh "${scriptPath}" "${sourceFilePath}" "${testFilePath}" "${coverageReportPath}";`); + terminal.sendText(`sh "${scriptPath}" "${sourceFilePath}" "${testFilePath}" "${coverageReportPath}" "${command}";`); - // Listen for terminal close event const disposable = vscode.window.onDidCloseTerminal(eventTerminal => { - console.log('Terminal closed'); if (eventTerminal === terminal) { - disposable.dispose(); // Dispose the listener - resolve(); // Resolve the promise + disposable.dispose(); + resolve(); } }); - - } catch (error) { + } catch (error) { console.log(error); vscode.window.showErrorMessage('Error occurred Keploy utg: ' + error); reject(error); @@ -72,25 +111,38 @@ async function ensureTestFileExists(sourceFilePath: string): Promise { vscode.window.showErrorMessage('No workspace is opened.'); return; } - const rootDir = path.dirname(vscode.workspace.workspaceFolders[0].uri.fsPath); // Root directory of the project - const testDir = path.join(rootDir, 'test'); - const relativeSourceFilePath = path.relative(rootDir, sourceFilePath); + // const rootDir = vscode.workspace.workspaceFolders[0].uri.fsPath; // Root directory of the project + const extension = path.extname(sourceFilePath); + const sourceDir = path.dirname(sourceFilePath); // Directory of the source file + const testDir = path.join(sourceDir, 'test'); // 'test' directory under the source directory const sourceFileName = path.basename(sourceFilePath); - const testFileName = sourceFileName.replace('.js', '.test.js'); - const testFilePath = path.join(testDir, relativeSourceFilePath.replace(sourceFileName, testFileName)); + let testFileName: string; + let testFileContent = ''; + + if (extension === '.js' || extension === '.ts') { + testFileName = sourceFileName.replace(extension, `.test${extension}`); + } else if (extension === '.py') { + testFileName = "test_" + sourceFileName; + } else if (extension === '.java') { + testFileName = sourceFileName.replace('.java', 'Test.java'); + } else if (extension === '.go') { + testFileName = sourceFileName.replace('.go', '_test.go'); + testFileContent = `package main\n\nimport "testing"`; + } else { + vscode.window.showErrorMessage(`Unsupported file type: ${extension}`); + return; + } + + const testFilePath = path.join(testDir, testFileName); + console.log(testFilePath, testDir, "testFilePath"); if (!fs.existsSync(testDir)) { fs.mkdirSync(testDir, { recursive: true }); } - const testFileDir = path.dirname(testFilePath); - if (!fs.existsSync(testFileDir)) { - fs.mkdirSync(testFileDir, { recursive: true }); - } - if (!fs.existsSync(testFilePath)) { - fs.writeFileSync(testFilePath, `// Test file for ${sourceFileName}`); + fs.writeFileSync(testFilePath, testFileContent); vscode.window.showInformationMessage(`Created test file: ${testFilePath}`); } else { vscode.window.showInformationMessage(`Test file already exists: ${testFilePath}`); diff --git a/src/extension.ts b/src/extension.ts index 3294ee9..c9d038e 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1,6 +1,6 @@ import * as vscode from 'vscode'; import { SidebarProvider } from './SidebarProvider'; -import SignIn, { validateFirst } from './SignIn'; +import SignIn, { validateFirst,SignInWithOthers } from './SignIn'; import oneClickInstall from './OneClickInstall'; import { getKeployVersion, getCurrentKeployVersion } from './version'; import { downloadAndUpdate, downloadAndUpdateDocker } from './updateKeploy'; @@ -9,6 +9,11 @@ import { getGitHubAccessToken, getMicrosoftAccessToken, getInstallationID } from import * as acorn from 'acorn'; import * as walk from 'acorn-walk'; import { SentryInit } from './sentryInit'; +import TreeSitter from 'tree-sitter'; +import TreeSitterJavaScript from 'tree-sitter-javascript'; +import TreeSitterPython from 'tree-sitter-python'; +import TreeSitterJava from 'tree-sitter-java'; +import TreeSitterGo from 'tree-sitter-go'; class KeployCodeLensProvider implements vscode.CodeLensProvider { onDidChangeCodeLenses?: vscode.Event | undefined; @@ -18,7 +23,15 @@ class KeployCodeLensProvider implements vscode.CodeLensProvider { token: vscode.CancellationToken ): vscode.CodeLens[] | Thenable { const fileName = document.uri.fsPath; - if (fileName.endsWith('.test.js') || fileName.endsWith('.test.ts')) { + if ( + fileName.endsWith('.test.js') || + fileName.endsWith('.test.ts') || + fileName.endsWith('Test.java') || // Check for Java test file ending + fileName.includes('/Test') || // Check for Java test file prefix in the path + fileName.includes('/test/') || // Skip files in a "tests" directory + fileName.endsWith('_test.go') || + fileName.includes('test_') + ) { return []; } @@ -26,30 +39,88 @@ class KeployCodeLensProvider implements vscode.CodeLensProvider { const codeLenses: vscode.CodeLens[] = []; try { - const ast = acorn.parse(text, { ecmaVersion: 2020, sourceType: 'module' }); + const parser = new TreeSitter(); + + if (fileName.endsWith('.js') || fileName.endsWith('.ts')) { + parser.setLanguage(TreeSitterJavaScript); + } else if (fileName.endsWith('.py')) { + parser.setLanguage(TreeSitterPython); + } else if (fileName.endsWith('.java')) { + parser.setLanguage(TreeSitterJava); + } else if (fileName.endsWith('.go')) { + parser.setLanguage(TreeSitterGo); + } else { + return codeLenses; // Return if file type is unsupported + } + + const tree = parser.parse(text); + const cursor = tree.walk(); - walk.fullAncestor(ast, (node: any, state: any, ancestors: any[]) => { - if (node.type === 'FunctionDeclaration' || node.type === 'FunctionExpression') { - const line = document.positionAt(node.start).line; + const traverseTree = (cursor: TreeSitter.TreeCursor, ancestors: TreeSitter.SyntaxNode[] = []) => { + const node = cursor.currentNode; + + if ( + (fileName.endsWith('.js') || fileName.endsWith('.ts')) && + (node.type === 'function_declaration' || node.type === 'function_expression') + ) { + const line = document.positionAt(node.startIndex).line; const range = new vscode.Range(line, 0, line, 0); codeLenses.push(new vscode.CodeLens(range, { title: '🐰 Generate unit tests', command: 'keploy.utg', arguments: [document.uri.fsPath] })); - } else if (node.type === 'ArrowFunctionExpression') { - const parent = ancestors[ancestors.length - 2]; - if (parent.type !== 'CallExpression' || (parent.callee.property?.name !== 'then' && parent.callee.property?.name !== 'catch')) { - const line = document.positionAt(node.start).line; - const range = new vscode.Range(line, 0, line, 0); - codeLenses.push(new vscode.CodeLens(range, { - title: '🐰 Generate unit tests', - command: 'keploy.utg', - arguments: [document.uri.fsPath] - })); + } else if (fileName.endsWith('.js') || fileName.endsWith('.ts')) { + if (node.type === 'arrow_function') { + const parent = ancestors[ancestors.length - 1]; + if (parent?.type !== 'CallExpression') { + const line = document.positionAt(node.startIndex).line; + const range = new vscode.Range(line, 0, line, 0); + codeLenses.push(new vscode.CodeLens(range, { + title: '🐰 Generate unit tests', + command: 'keploy.utg', + arguments: [document.uri.fsPath] + })); + } } + } else if (fileName.endsWith('.py') && node.type === 'function_definition') { + const line = document.positionAt(node.startIndex).line; + const range = new vscode.Range(line, 0, line, 0); + codeLenses.push(new vscode.CodeLens(range, { + title: '🐰 Generate unit tests', + command: 'keploy.utg', + arguments: [document.uri.fsPath] + })); + } else if (fileName.endsWith('.java') && (node.type === 'method_declaration' || node.type === 'constructor_declaration')) { + const line = document.positionAt(node.startIndex).line; + const range = new vscode.Range(line, 0, line, 0); + codeLenses.push(new vscode.CodeLens(range, { + title: '🐰 Generate unit tests', + command: 'keploy.utg', + arguments: [document.uri.fsPath] + })); + } else if (fileName.endsWith('.go') && (node.type === 'function_declaration' || node.type === 'method_declaration')) { + const line = document.positionAt(node.startIndex).line; + const range = new vscode.Range(line, 0, line, 0); + codeLenses.push(new vscode.CodeLens(range, { + title: '🐰 Generate unit tests', + command: 'keploy.utg', + arguments: [document.uri.fsPath] + })); } - }); + + // Traverse to the first child node + if (cursor.gotoFirstChild()) { + traverseTree(cursor, ancestors.concat(node)); + cursor.gotoParent(); // Go back to parent after finishing with the child + } + // Traverse to the next sibling node + if (cursor.gotoNextSibling()) { + traverseTree(cursor, ancestors); + } + }; + + traverseTree(cursor); } catch (error) { console.error(error); @@ -63,6 +134,11 @@ const Sentry = SentryInit() export function activate(context: vscode.ExtensionContext) { const sidebarProvider = new SidebarProvider(context.extensionUri); context.subscriptions.push( + vscode.window.registerUriHandler({ + handleUri(uri: vscode.Uri): vscode.ProviderResult { + vscode.window.showInformationMessage(`URI handler called: ${uri.toString()}`); + } + }), vscode.window.registerWebviewViewProvider( "Keploy-Sidebar", sidebarProvider @@ -74,7 +150,20 @@ export function activate(context: vscode.ExtensionContext) { vscode.languages.registerCodeLensProvider( { language: 'typescript', scheme: 'file' }, new KeployCodeLensProvider() - ) + ), + vscode.languages.registerCodeLensProvider( + { language: 'python', scheme: 'file' }, + new KeployCodeLensProvider() + ), + vscode.languages.registerCodeLensProvider( + { language: 'go', scheme: 'file' }, + new KeployCodeLensProvider() + ), + vscode.languages.registerCodeLensProvider( + { language: 'java', scheme: 'file' }, + new KeployCodeLensProvider() + ), + ); @@ -132,7 +221,31 @@ export function activate(context: vscode.ExtensionContext) { } }); + let signInWithOthersCommand = vscode.commands.registerCommand('keploy.SignInWithOthers', async () => { + try { + const result = await SignInWithOthers(); + const accessToken = result as string; // Assert that result is a string + getInstallationID(); + + await context.globalState.update('accessToken', accessToken); + + if (Boolean(accessToken)) { + vscode.window.showInformationMessage('You are now signed in!'); + vscode.commands.executeCommand('setContext', 'keploy.signedIn', true); + vscode.commands.executeCommand('setContext', 'keploy.signedOut', false); + } else { + console.log('Validation failed for the user !'); + vscode.window.showInformationMessage('Failed to sign in Keploy!'); + } + } catch (error) { + // console.error('Error during sign-in:', error); + vscode.window.showInformationMessage('Failed to sign in Keploy!'); + } + }); + context.subscriptions.push(signInCommand); + context.subscriptions.push(signInWithOthersCommand); + } @@ -250,7 +363,6 @@ export function activate(context: vscode.ExtensionContext) { }); context.subscriptions.push(disposable); - }