Skip to content

Commit 211cc16

Browse files
committed
Update tests
Signed-off-by: Rahul Krishna <i.m.ralk@gmail.com>
1 parent d1dc8e1 commit 211cc16

File tree

5 files changed

+46
-60
lines changed

5 files changed

+46
-60
lines changed

package.json

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "cldk",
3-
"version": "1.0.0",
3+
"version": "0.1.0",
44
"description": "",
55
"main": "dist/index.js",
66
"scripts": {
@@ -17,24 +17,19 @@
1717
],
1818
"devDependencies": {
1919
"@types/bun": "^1.2.10",
20-
"@types/extract-zip": "^2.0.3",
20+
"@types/extract-zip": "2.0.0",
2121
"@types/node": "^22.14.1",
2222
"typescript": "^5.8.3"
2323
},
2424
"private": true,
25-
"volta": {
26-
"node": "22.14.0",
27-
"pnpm": "10.8.1",
28-
"yarn": "4.9.1"
29-
},
3025
"dependencies": {
26+
"@types/jsonstream": "^0.8.33",
3127
"JSONStream": "^1.3.5",
3228
"bun": "^1.2.10",
3329
"extract-zip": "^2.0.1",
3430
"fast-glob": "^3.3.3",
31+
"graphology": "^0.26.0",
3532
"loglevel": "^1.9.2",
36-
"rimraf": "^6.0.1",
37-
"yarn": "^1.22.22",
3833
"zod": "^3.24.3"
3934
},
4035
"testing": {

src/CLDK.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,17 @@ export class CLDK {
1818
return new CLDK(language);
1919
}
2020

21+
/**
22+
* Get the programming language of the CLDK instance
23+
*/
24+
public getLanguage(): string {
25+
return this.language;
26+
}
27+
2128
/**
2229
* Implementation of the analysis method
2330
*/
24-
Implementation
25-
public analysis({ projectPath, analysisLevel}): JavaAnalysis {
31+
public analysis({ projectPath, analysisLevel }: { projectPath: string, analysisLevel: string }): JavaAnalysis {
2632
if (this.language === "java") {
2733
this.makeSureJavaIsInstalled();
2834
return new JavaAnalysis({
@@ -41,7 +47,7 @@ export class CLDK {
4147
throw result.error;
4248
}
4349
if (result.status !== 0) {
44-
throw new Error(result.stderr || "Java is not installed.");
50+
throw new Error(result.stderr || "Java is not installed. Please install Java 11+ to be able to analyze java projects.");
4551
}
4652
} catch (e: any) {
4753
throw new Error(e.message || String(e));

src/analysis/java/JavaAnalysis.ts

Lines changed: 32 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,12 @@ import path from "path";
1818
import fg from "fast-glob";
1919
import fs from "fs";
2020
import log from "loglevel";
21-
import {spawn, spawnSync} from "node:child_process";
22-
import {JApplication} from "../../models/java";
23-
import {JApplicationType, JCompilationUnitType} from "../../models/java/types";
21+
import { spawnSync } from "node:child_process";
22+
import { JApplication } from "../../models/java";
23+
import * as types from "../../models/java/types";
2424
import os from "os";
2525
import JSONStream from "JSONStream";
26+
import crypto from "crypto";
2627

2728
enum AnalysisLevel {
2829
SYMBOL_TABLE = "1",
@@ -38,8 +39,8 @@ const analysisLevelMap: Record<string, AnalysisLevel> = {
3839

3940
export class JavaAnalysis {
4041
private readonly projectDir: string | null;
41-
private readonly analysisLevel: AnalysisLevel;
42-
application?: JApplicationType;
42+
private analysisLevel: AnalysisLevel;
43+
application?: types.JApplicationType;
4344

4445
constructor(options: { projectDir: string | null; analysisLevel: string }) {
4546
this.projectDir = options.projectDir;
@@ -64,26 +65,24 @@ export class JavaAnalysis {
6465
/**
6566
* Initialize the application by running the codeanalyzer and parsing the output.
6667
* @private
67-
* @returns {Promise<JApplicationType>} A promise that resolves to the parsed application data
68+
* @returns {Promise<types.JApplicationType>} A promise that resolves to the parsed application data
6869
* @throws {Error} If the project directory is not specified or if codeanalyzer fails
6970
*/
70-
private async _initialize_application(): Promise<JApplicationType> {
71-
return new Promise<JApplicationType>((resolve, reject) => {
71+
private async _initialize_application(): Promise<types.JApplicationType> {
72+
return new Promise<types.JApplicationType>((resolve, reject) => {
7273
if (!this.projectDir) {
7374
return reject(new Error("Project directory not specified"));
7475
}
7576

7677
const projectPath = path.resolve(this.projectDir);
77-
/**
78-
* I kept running into OOM issues when running the codeanalyzer output is piped to stream.
79-
* So I decided to write the output to a temporary file and then read the file.
80-
*/
81-
// Create a temporary file to store the codeanalyzer output
82-
const crypto = require('crypto');
78+
// Create a temporary file to store the codeanalyzer output
8379
const tmpFilePath = path.join(os.tmpdir(), `${Date.now()}-${crypto.randomUUID()}`);
8480
const command = [...this.getCodeAnalyzerExec(), "-i", projectPath, '-o', tmpFilePath, `--analysis-level=${this.analysisLevel}`, '--verbose'];
81+
// Check if command is valid
82+
if (!command[0]) {
83+
return reject(new Error("Codeanalyzer command not found"));
84+
}
8585
log.debug(command.join(" "));
86-
8786
const result = spawnSync(command[0], command.slice(1), {
8887
stdio: ["ignore", "pipe", "inherit"],
8988
});
@@ -99,9 +98,9 @@ export class JavaAnalysis {
9998
// Read the analysis result from the temporary file
10099
try {
101100
const stream = fs.createReadStream(path.join(tmpFilePath, 'analysis.json')).pipe(JSONStream.parse());
102-
const result = {} as JApplicationType;
101+
const result = {} as types.JApplicationType;
103102

104-
stream.on('data', (data) => {
103+
stream.on('data', (data: unknown) => {
105104
Object.assign(result, JApplication.parse(data));
106105
});
107106

@@ -110,10 +109,10 @@ export class JavaAnalysis {
110109
fs.rm(tmpFilePath, {recursive: true, force: true}, (err) => {
111110
if (err) log.warn(`Failed to delete temporary file: ${tmpFilePath}`, err);
112111
});
113-
resolve(result as JApplicationType);
112+
resolve(result as types.JApplicationType);
114113
});
115114

116-
stream.on('error', (err) => {
115+
stream.on('error', (err: any) => {
117116
reject(err);
118117
});
119118
} catch (error) {
@@ -135,17 +134,28 @@ export class JavaAnalysis {
135134
*
136135
* The application view denoted by this application structure is crucial for further fine-grained analysis APIs.
137136
* If the application is not already initialized, it will be initialized first.
138-
* @returns {Promise<JApplicationType>} A promise that resolves to the application data
137+
* @returns {Promise<types.JApplicationType>} A promise that resolves to the application data
139138
*/
140-
public async getApplication(): Promise<JApplicationType> {
139+
public async getApplication(): Promise<types.JApplicationType> {
141140
if (!this.application) {
142141
this.application = await this._initialize_application();
143142
}
144143
return this.application;
145144
}
146145

147-
public async getSymbolTable(): Promise<Record<string, JCompilationUnitType>> {
146+
public async getSymbolTable(): Promise<Record<string, types.JCompilationUnitType>> {
148147
return (await this.getApplication()).symbol_table;
149148
}
149+
150+
public async getCallGraph(): Promise<JCallGraph> {
151+
const application = await this.getApplication();
152+
if (application.call_graph === undefined || application.call_graph === null) {
153+
log.debug("Re-initializing application with call graph");
154+
this.analysisLevel = AnalysisLevel.CALL_GRAPH;
155+
this.application = await this._initialize_application();
156+
}
157+
158+
}
159+
150160
}
151161

src/analysis/java/interfaces.ts

Lines changed: 0 additions & 21 deletions
This file was deleted.

test/unit/CLDK.test.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { test, expect } from "bun:test";
55
* These first set of tests are to test the CLDK class
66
*/
77
test("CLDK initialization with Java language", () => {
8-
expect(CLDK.for("java").language).toBe("java");
8+
expect(CLDK.for("java").getLanguage()).toBe("java");
99
});
1010

1111
test("CLDK must throw and error when the language is not Java", () => {
@@ -19,7 +19,6 @@ test("CLDK must throw and error when the language is not Java", () => {
1919
test("CLDK Analysis level must be set to 1 for symbol table", () => {
2020
const analysis = CLDK.for("java").analysis({
2121
projectPath: "fake/path",
22-
sourceCode: null,
2322
analysisLevel: "Symbol Table",
2423
});
2524
expect(analysis.analysisLevel).toBe("1");
@@ -28,7 +27,6 @@ test("CLDK Analysis level must be set to 1 for symbol table", () => {
2827
test("CLDK Analysis level must be set to 2 for call graph", () => {
2928
const analysis = CLDK.for("java").analysis({
3029
projectPath: "fake/path",
31-
sourceCode: null,
3230
analysisLevel: "Call Graph",
3331
});
3432
expect(analysis.analysisLevel).toBe("2");
@@ -37,7 +35,6 @@ test("CLDK Analysis level must be set to 2 for call graph", () => {
3735
test("CLDK Analysis level must be set to 3 for system dependency graph", () => {
3836
const analysis = CLDK.for("java").analysis({
3937
projectPath: "fake/path",
40-
sourceCode: null,
4138
analysisLevel: "system dependency graph",
4239
});
4340
expect(analysis.analysisLevel).toBe("3");
@@ -49,7 +46,6 @@ test("CLDK Analysis level must be set to 3 for system dependency graph", () => {
4946
test("CLDK must get the correct codeanalyzer execution command", () => {
5047
const analysis = CLDK.for("java").analysis({
5148
projectPath: "fake/path",
52-
sourceCode: null,
5349
analysisLevel: "Symbol Table",
5450
});
5551

0 commit comments

Comments
 (0)