• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

codellm-devkit / typescript-sdk / 14708801765

28 Apr 2025 01:19PM UTC coverage: 83.75%. First build
14708801765

Pull #2

github

web-flow
Merge e38213221 into 2ebd6e0b6
Pull Request #2: Lightweight APIs to analyze Java projects just with it's Symbol Table

167 of 191 new or added lines in 5 files covered. (87.43%)

402 of 480 relevant lines covered (83.75%)

41.01 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

85.51
/src/analysis/java/JavaAnalysis.ts
1
/**
2
 * Copyright IBM Corporation 2025
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 *       http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16

17
import path from "path";
58✔
18
import fg from "fast-glob";
54✔
19
import fs from "fs";
50✔
20
import { spawnSync } from "node:child_process";
94✔
21
import { JApplication, JCompilationUnit } from "../../models/java";
98✔
22
import * as types from "../../models/java/types";
23
import { JType } from "../../models/java";
24
import os from "os";
50✔
25
import JSONStream from "JSONStream";
72✔
26
declare module "JSONStream";
27
import crypto from "crypto";
66✔
28
import { createLogger } from "src/utils";
82✔
29

30
const logger = createLogger("JavaAnalysis");
88✔
31

32
enum AnalysisLevel {
126✔
33
  SYMBOL_TABLE = "1",
76✔
34
  CALL_GRAPH = "2",
72✔
35
  SYSTEM_DEPENDENCY_GRAPH = "3",
100✔
36
}
37

38
const analysisLevelMap: Record<string, AnalysisLevel> = {
54✔
39
  "symbol table": AnalysisLevel.SYMBOL_TABLE,
82✔
40
  "call graph": AnalysisLevel.CALL_GRAPH,
74✔
41
  "system dependency graph": AnalysisLevel.SYSTEM_DEPENDENCY_GRAPH,
122✔
42
};
3✔
43

44
export class JavaAnalysis {
45
  private readonly projectDir: string | null;
46
  private analysisLevel: AnalysisLevel;
47
  application?: types.JApplicationType;
1✔
48

49
  constructor(options: { projectDir: string | null; analysisLevel: string }) {
41✔
50
    this.projectDir = options.projectDir;
82✔
51
    this.analysisLevel = analysisLevelMap[options.analysisLevel.toLowerCase()] ?? AnalysisLevel.SYMBOL_TABLE;
210✔
52
  }
53

54
  private getCodeAnalyzerExec(): string[] {
35✔
55
    const codeanalyzerJarPath = path.resolve(__dirname, "jars");
128✔
56
    const pattern = path.join(codeanalyzerJarPath, "**/codeanalyzer-*.jar").replace(/\\/g, "/");
192✔
57
    const matches = fg.sync(pattern);
74✔
58
    const jarPath = matches[0];
62✔
59

60
    if (!jarPath) {
34✔
61
      logger.error("Default codeanalyzer jar not found.");
58✔
62
      throw new Error("Default codeanalyzer jar not found.");
59✔
63
    }
9✔
64
    logger.info("Codeanalyzer jar found at:", jarPath);
110✔
65
    return ["java", "-jar", jarPath];
76✔
66
  }
67

68
  /**
69
   * Initialize the application by running the codeanalyzer and parsing the output.
70
   * @private
71
   * @returns {Promise<types.JApplicationType>} A promise that resolves to the parsed application data
72
   * @throws {Error} If the project directory is not specified or if codeanalyzer fails
73
   */
74
  private async _initialize_application(): Promise<types.JApplicationType> {
39✔
75
    return new Promise<types.JApplicationType>((resolve, reject) => {
93✔
76
      if (!this.projectDir) {
52✔
77
        return reject(new Error("Project directory not specified"));
66✔
78
      }
13✔
79

80
      const projectPath = path.resolve(this.projectDir);
112✔
81
      // Create a temporary file to store the codeanalyzer output
82
      const tmpFilePath = path.join(os.tmpdir(), `${Date.now()}-${crypto.randomUUID()}`);
178✔
83
      const command = [
50✔
84
        ...this.getCodeAnalyzerExec(),
76✔
85
        "--input",
36✔
86
        projectPath,
40✔
87
        "--output",
38✔
88
        tmpFilePath,
40✔
89
        `--analysis-level=${this.analysisLevel}`,
98✔
90
        "--verbose",
34✔
91
      ];
16✔
92
      // Check if command is valid
93
      if (!command[0]) {
42✔
94
        return reject(new Error("Codeanalyzer command not found"));
65✔
95
      }
13✔
96
      logger.debug(command.join(" "));
76✔
97
      const result = spawnSync(command[0], command.slice(1), {
128✔
98
        stdio: ["ignore", "pipe", "inherit"],
84✔
99
      });
18✔
100

101
      if (result.error) {
44✔
102
        return reject(result.error);
34✔
103
      }
13✔
104

105
      if (result.status !== 0) {
58✔
106
        return reject(new Error("Codeanalyzer failed to run."));
62✔
107
      }
13✔
108

109
      // Read the analysis result from the temporary file
110
      try {
26✔
111
        const stream = fs.createReadStream(path.join(tmpFilePath, "analysis.json")).pipe(JSONStream.parse());
218✔
112
        const result = {} as types.JApplicationType;
52✔
113

114
        stream.on("data", (data: unknown) => {
77✔
115
          Object.assign(result, JApplication.parse(data));
113✔
116
        });
20✔
117

118
        stream.on("end", () => {
67✔
119
          // Clean up the temporary file
120
          logger.debug(`Deleting temporary file: ${tmpFilePath}`);
132✔
121
          fs.rm(tmpFilePath, { recursive: true, force: true }, (err) => {
149✔
122
            if (err) logger.warn(`Failed to delete temporary file: ${tmpFilePath}`, err);
132✔
123
          });
24✔
124
          resolve(result as types.JApplicationType);
49✔
125
        });
20✔
126

NEW
127
        stream.on("error", (err: any) => {
×
128
          reject(err);
21✔
129
        });
18✔
130
      } catch (error) {
23✔
131
        reject(error);
30✔
132
      }
133
    });
15✔
134
  }
135

136
  /**
137
   * Get the application data. This method returns the parsed Java application as a JSON structure containing the
138
   * following information:
139
   * |_ symbol_table: A record of file paths to compilation units. Each compilation unit further contains:
140
   *   |_ comments: Top-level file comments
141
   *   |_ imports: All import statements
142
   *   |_ type_declarations: All class/interface/enum/record declarations with their:
143
   *     |_ fields, methods, constructors, initialization blocks, etc.
144
   * |_ call_graph: Method-to-method call relationships (if analysis level ≥ 2)
145
   * |_ system_dependency_graph: System component dependencies (if analysis level = 3)
146
   *
147
   * The application view denoted by this application structure is crucial for further fine-grained analysis APIs.
148
   * If the application is not already initialized, it will be initialized first.
149
   * @returns {Promise<types.JApplicationType>} A promise that resolves to the application data
150
   */
151
  public async getApplication(): Promise<types.JApplicationType> {
30✔
152
    if (!this.application) {
59✔
153
      this.application = await this._initialize_application();
120✔
154
    }
9✔
155
    return this.application;
59✔
156
  }
157

158
  /**
159
   * Get the symbol table from the application.
160
   * @returns {Promise<Record<string, types.JCompilationUnitType>>} A promise that resolves to a record of file paths and their
161
   * corresponding {@link JCompilationUnitType} objects
162
   * 
163
   * @notes This method retrieves the symbol table from the application, which contains information about all
164
   * compilation units in the Java application. The returned record contains file paths as keys and their
165
   * corresponding {@link JCompilationUnit} objects as values.
166
   */
167
  public async getSymbolTable(): Promise<Record<string, types.JCompilationUnitType>> {
30✔
168
    return (await this.getApplication()).symbol_table;
111✔
169
  }
170

171
  /**
172
   * Get all classes in the application.
173
   * @returns {Promise<Record<string, types.JTypeType>>} A promise that resolves to a record of class names and their
174
   * corresponding {@link JTypeType} objects
175
   *
176
   * @notes This method retrieves all classes from the symbol table and returns them as a record. The returned record
177
   *       contains class names as keys and their corresponding {@link JType} objects as values.
178
   */
179
  public async getAllClasses(): Promise<Record<string, types.JTypeType>> {
29✔
180
    return Object.values(await this.getSymbolTable()).reduce((classAccumulator, symbol) => {
187✔
181
      Object.entries(symbol.type_declarations).forEach(([key, value]) => {
151✔
182
        classAccumulator[key] = value;
73✔
183
      });
16✔
184
      return classAccumulator;
56✔
185
    }, {} as Record<string, types.JTypeType>);
19✔
186
  }
187

188
  /**
189
   * Get a specific class by its qualified name.
190
   * @param {string} qualifiedName - The qualified name of the class to retrieve
191
   * @returns {Promise<types.JTypeType>} A promise that resolves to the {@link JTypeType} object representing the class
192
   * @throws {Error} If the class is not found in the application
193
   * 
194
   * @notes This method retrieves a specific class from the application by its qualified name. If the class is found,
195
   *       it returns the corresponding {@link JType} object. If the class is not found, it throws an error.
196
   */
197
  public async getClassByQualifiedName(qualifiedName: string): Promise<types.JTypeType> {
65✔
198
    const allClasses = await this.getAllClasses();
100✔
199
    if (allClasses[qualifiedName]) {
75✔
200
      return allClasses[qualifiedName];
69✔
201
    }
3✔
202
    else
203
      throw new Error(`Class ${qualifiedName} not found in the application.`);
175✔
204
  }
205

206
  /**
207
  * Get all methods in the application.
208
  * @returns {Promise<Record<string, Record<string, types.JCallableType>>>} A promise that resolves to a record of
209
  * method names and their corresponding {@link JCallableType} objects
210
  * 
211
  * @notes This method retrieves all methods from the symbol table and returns them as a record. The returned
212
  *       record contains class names as keys and their corresponding {@link JCallableType} objects as values.
213
  *       Each {@link JCallableType} object contains information about the method's parameters, return type, and
214
  *       other relevant details.
215
  */
216
  public async getAllMethods(): Promise<Record<string, Record<string, types.JCallableType>>> {
29✔
217
    return Object.entries(await this.getAllClasses()).reduce((allMethods, [key, value]) => {
187✔
218
      allMethods[key] = value.callable_declarations;
104✔
219
      return allMethods;
44✔
220
    }, {} as Record<string, Record<string, types.JCallableType>>);
19✔
221
  }
222

223
  /**
224
   * Get all methods in a specific class in the application.
225
   * @returns {Promise<Record<string, Record<string, types.JCallableType>>>} A promise that resolves to a record of
226
   * method names and their corresponding {@link JCallableType} objects
227
   * 
228
   * @notes This method retrieves all methods from the symbol table and returns them as a record. The returned
229
   *       record contains class names as keys and their corresponding {@link JCallableType} objects as values.
230
   *       Each {@link JCallableType} object contains information about the method's parameters, return type, and
231
   *       other relevant details.
232
   */
233
  public async getAllMethodsByClass(qualifiedName: string): Promise<Array<types.JCallableType>> {
62✔
234
    const classForWhichMethodsAreRequested = await this.getClassByQualifiedName(qualifiedName);
190✔
235
    return classForWhichMethodsAreRequested ? Object.values(classForWhichMethodsAreRequested.callable_declarations ?? {}) : [];
251✔
236
  }
237

238
  /**
239
   * Get a specific methods within a specific class by its qualified name.
240
   * @param {string} qualifiedName - The qualified name of the class to retrieve
241
   * @param {string} methodName - The name of the method to retrieve
242
   * @returns {Promise<types.JCallableType>} A promise that resolves to the {@link JCallable} object representing the method.
243
   * @throws {Error} If the class or method is not found in the application.
244
   * 
245
   * @notes This method retrieves a specific method from the application by its qualified name and method name.
246
   * If the method is found, it returns the corresponding {@link JCallableType} object. If the method is not found,
247
   * it throws an error.
248
   */
249
  public async getMethodByQualifiedName(qualifiedName: string, methodName: string): Promise<types.JCallableType> {
90✔
250
    return (await this.getAllMethodsByClass(qualifiedName)).find(
121✔
251
      (method) => method.signature === methodName
85✔
252
    ) ?? (() => { throw new Error(`Method ${methodName} not found in class ${qualifiedName}.`); })();
125✔
253
  }
254

255
  /**
256
   * Get all the method parameters in a specific method within a specific class by its qualified name.
257
   * @param {string} qualifiedName - The qualified name of the class to retrieve
258
   * @param {string} methodName - The name of the method to retrieve
259
   * @returns {Promise<Array<types.JCallableParameterType>>} A promise that resolves to an array of {@link JCallableParameterType} objects
260
   * @throws {Error} If the class or method is not found in the application.
261
   * 
262
   * @notes This method retrieves all the parameters of a specific method from the application by its qualified name
263
   * and method name. If the method is found, it returns an array of {@link JCallableParameter} objects representing
264
   */
265
  public async getMethodParameters(qualifiedName: string, methodName: string): Promise<Array<types.JCallableParameterType>> {
85✔
266
    return (await this.getMethodByQualifiedName(qualifiedName, methodName)).parameters ?? [];
189✔
267
  }
268

269
  /**
270
   * Get all the method parameters in a specific method within a specific class by its callable object.
271
   * @param {types.JCallableType} callable - The callable object representing the method to retrieve
272
   * @returns {Promise<Array<types.JCallableParameterType>>} A promise that resolves to an array of {@link JCallableParameterType} objects
273
   * 
274
   * @notes This method retrieves all the parameters of a specific method from the application by its callable object.
275
   * If the method is found, it returns an array of {@link JCallableParameter} objects representing
276
   * the parameters of the method. Otherwise, it returns an empty array.
277
   */
278
  public async getMethodParametersFromCallable(callable: types.JCallableType): Promise<Array<types.JCallableParameterType>> {
63✔
NEW
279
    return callable.parameters ?? [];
×
NEW
280
  }
×
NEW
281

×
NEW
282
  /**
×
NEW
283
   * Get the java file path given the qualified name of the class.
×
NEW
284
   * @param {string} qualifiedName - The qualified name of the class to retrieve
×
NEW
285
   * @returns {Promise<string>} A promise that resolves to the file path of the Java file containing the class
×
NEW
286
   * @throws {Error} If the class is not found in the application.
×
NEW
287
   * 
×
NEW
288
   * @notes This method retrieves the file path of the Java file containing the class with the specified qualified name.
×
NEW
289
   * If the class is found, it returns the file path as a string. If the class is not found, it throws an error.
×
NEW
290
   */
×
NEW
291
  public async getJavaFilePathByQualifiedName(qualifiedName: string): Promise<string> {
×
NEW
292
    const symbolTable = await this.getSymbolTable();
×
NEW
293
    for (const [filePath, compilationUnit] of Object.entries(symbolTable)) {
×
NEW
294
      if (Object.keys(compilationUnit.type_declarations).includes(qualifiedName)) {
×
NEW
295
        return filePath;
×
NEW
296
      }
×
NEW
297
    }
×
298
    throw new Error(`Class ${qualifiedName} not found in the application.`);
77✔
299
  }
300
}
66✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc