Skip to content

Commit 9c9d811

Browse files
Merge pull request #4 from olasunkanmi-SE/development
Add support for ArrowFunction types in TNode and create new type defi…
2 parents e37b413 + 82985b3 commit 9c9d811

File tree

4 files changed

+175
-47
lines changed

4 files changed

+175
-47
lines changed

src/Example/example.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,5 @@ import { TypeScriptCodeMapper } from "../services/typescript-code-mapper.service
22

33
export async function getCodeBase() {
44
const code = new TypeScriptCodeMapper();
5-
let x = await code.buildCodebaseMap();
6-
return x;
5+
return await code.buildCodebaseMap();
76
}

src/interfaces/generic.interface.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,24 @@ export type TNode =
6969
| ts.FunctionDeclaration
7070
| ts.MethodDeclaration
7171
| ts.InterfaceDeclaration
72-
| ts.EnumDeclaration;
72+
| ts.EnumDeclaration
73+
| ts.ArrowFunction;
74+
75+
export type DeclarationOrFunctionNode =
76+
| ts.FunctionDeclaration
77+
| ts.MethodDeclaration
78+
| ts.ParameterDeclaration
79+
| ts.PropertyDeclaration
80+
| ts.PropertySignature
81+
| ts.ArrowFunction;
82+
83+
export type DeclarationFunctionNode =
84+
| ts.FunctionDeclaration
85+
| ts.MethodDeclaration
86+
| ts.ArrowFunction
87+
| (ts.ClassElement & ts.FunctionDeclaration)
88+
| (ts.ClassElement & ts.ArrowFunction)
89+
| (ts.ClassElement & ts.MethodDeclaration);
7390

7491
export interface IProperty {
7592
name: string;

src/interfaces/ts.code.mapper.interface.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ export interface ITypeScriptCodeMapper {
9191
* @param sourceFile The source file that contains the node to be printed.
9292
* @returns A string representation of the given node.
9393
*/
94-
getPrintedNode(
94+
getFunctionNodeText(
9595
node: ts.FunctionDeclaration | ts.MethodDeclaration,
9696
sourceFile: ts.SourceFile
9797
): string;

src/services/typescript-code-mapper.service.ts

Lines changed: 155 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import { glob } from "glob";
33
import * as path from "path";
44
import * as ts from "typescript";
55
import {
6+
DeclarationFunctionNode,
7+
DeclarationOrFunctionNode,
68
IClassInfo,
79
ICodebaseMap,
810
IEnumInfo,
@@ -17,8 +19,8 @@ import { ITypeScriptCodeMapper } from "../interfaces/ts.code.mapper.interface";
1719
import { Result } from "../result";
1820

1921
export class TypeScriptCodeMapper implements ITypeScriptCodeMapper {
20-
public program: ts.Program | undefined;
21-
public typeChecker: ts.TypeChecker | undefined;
22+
private program: ts.Program | undefined;
23+
private typeChecker: ts.TypeChecker | undefined;
2224

2325
constructor() {
2426
this.initializeTypescriptProgram();
@@ -28,7 +30,7 @@ export class TypeScriptCodeMapper implements ITypeScriptCodeMapper {
2830
* Initializes a TypeScript program by reading the TS configuration file and creating a new program instance.
2931
* This method sets up the program and type checker for further compilation and analysis.
3032
*/
31-
private initializeTypescriptProgram() {
33+
private initializeTypescriptProgram(): void {
3234
try {
3335
const rootDir: string = process.cwd();
3436
const tsConfigPath: string = path.join(rootDir, "tsconfig.json");
@@ -46,7 +48,7 @@ export class TypeScriptCodeMapper implements ITypeScriptCodeMapper {
4648
compilerOptions.options
4749
);
4850

49-
this.typeChecker = this.program.getTypeChecker();
51+
this.typeChecker = this.getTypeChecker();
5052
} catch (error: any) {
5153
logError(error, "initializeTypescriptProgram", "");
5254
throw Error(error);
@@ -86,6 +88,56 @@ export class TypeScriptCodeMapper implements ITypeScriptCodeMapper {
8688
}
8789
}
8890

91+
private aggregateFunctions(
92+
node: DeclarationFunctionNode,
93+
sourceFile: ts.SourceFile,
94+
info: IClassInfo | IModuleInfo
95+
): void {
96+
const functionInfo: IFunctionInfo | null =
97+
this.getFunctionDetails(node, sourceFile)?.getValue() ?? null;
98+
if (functionInfo) {
99+
info?.functions?.push(functionInfo);
100+
}
101+
}
102+
103+
private aggergateProperties(
104+
node: ts.PropertyDeclaration,
105+
sourceFile: ts.SourceFile,
106+
info: IClassInfo | IModuleInfo
107+
) {
108+
const propertyInfo = this.extractPropertyParameters(
109+
node,
110+
sourceFile
111+
).getValue();
112+
if (propertyInfo) {
113+
info?.properties?.push(propertyInfo);
114+
}
115+
}
116+
117+
private aggregateInterfaces(
118+
node: ts.InterfaceDeclaration,
119+
sourceFile: ts.SourceFile,
120+
info: IClassInfo | IModuleInfo
121+
) {
122+
const interfaceInfo = this.extractInterfaceInfo(
123+
node,
124+
sourceFile
125+
).getValue();
126+
if (interfaceInfo) {
127+
info?.interfaces?.push(interfaceInfo);
128+
}
129+
}
130+
131+
private aggregateEnums(
132+
node: ts.EnumDeclaration,
133+
sourceFile: ts.SourceFile,
134+
info: IClassInfo | IModuleInfo
135+
) {
136+
const enumInfo = this.extractEnumInfo(node, sourceFile).getValue();
137+
if (enumInfo) {
138+
info?.enums?.push(enumInfo);
139+
}
140+
}
89141
/**
90142
* Retrieves and processes child elements of a class declaration, extracting
91143
* relevant information about methods, properties, interfaces, and enums.
@@ -97,48 +149,30 @@ export class TypeScriptCodeMapper implements ITypeScriptCodeMapper {
97149
* @param classInfo The object to store extracted class information.
98150
*/
99151
private processClassMembers(
100-
node: ts.ClassDeclaration | ts.Node,
152+
node: ts.ClassDeclaration,
101153
sourceFile: ts.SourceFile,
102154
info: IClassInfo | IModuleInfo,
103155
member?: ts.ClassElement
104156
): void {
105157
const currentElement = member ? member : node;
106158
if (
107159
ts.isMethodDeclaration(currentElement) ||
108-
ts.isFunctionDeclaration(currentElement)
160+
ts.isFunctionDeclaration(currentElement) ||
161+
ts.isArrowFunction(currentElement)
109162
) {
110-
const functionInfo: IFunctionInfo | null =
111-
this.getFunctionDetails(currentElement, sourceFile)?.getValue() ?? null;
112-
if (functionInfo) {
113-
info?.functions?.push(functionInfo);
114-
}
163+
this.aggregateFunctions(currentElement, sourceFile, info);
115164
}
116165

117166
if (ts.isPropertyDeclaration(currentElement)) {
118-
const propertyInfo = this.extractPropertyParameters(
119-
currentElement,
120-
sourceFile
121-
).getValue();
122-
if (propertyInfo) {
123-
info?.properties?.push(propertyInfo);
124-
}
167+
this.aggergateProperties(currentElement, sourceFile, info);
125168
}
126169

127170
if (ts.isInterfaceDeclaration(node)) {
128-
const interfaceInfo = this.extractInterfaceInfo(
129-
node,
130-
sourceFile
131-
).getValue();
132-
if (interfaceInfo) {
133-
info?.interfaces?.push(interfaceInfo);
134-
}
171+
this.aggregateInterfaces(node, sourceFile, info);
135172
}
136173

137174
if (ts.isEnumDeclaration(node)) {
138-
const enumInfo = this.extractEnumInfo(node, sourceFile).getValue();
139-
if (enumInfo) {
140-
info?.enums?.push(enumInfo);
141-
}
175+
this.aggregateEnums(node, sourceFile, info);
142176
}
143177
}
144178

@@ -189,7 +223,24 @@ export class TypeScriptCodeMapper implements ITypeScriptCodeMapper {
189223
* @returns An array of function parameter objects.
190224
*/
191225
extractFunctionParameters(
192-
node: ts.FunctionDeclaration | ts.MethodDeclaration,
226+
node: ts.FunctionDeclaration | ts.MethodDeclaration | ts.ArrowFunction,
227+
sourceFile: ts.SourceFile
228+
): Result<IProperty[]> {
229+
const properties = node.parameters.map((param) => {
230+
const name = param.name.getText(sourceFile);
231+
const type = param.type
232+
? this.getTypeAtLocation(param).getValue()
233+
: undefined;
234+
return {
235+
name,
236+
type,
237+
};
238+
});
239+
return Result.ok(properties);
240+
}
241+
242+
extractArrowFunctionParameters(
243+
node: ts.ArrowFunction,
193244
sourceFile: ts.SourceFile
194245
): Result<IProperty[]> {
195246
const properties = node.parameters.map((param) => {
@@ -213,7 +264,7 @@ export class TypeScriptCodeMapper implements ITypeScriptCodeMapper {
213264
* @returns An object containing function details, or null if the node has no name.
214265
*/
215266
getFunctionDetails(
216-
node: ts.FunctionDeclaration | ts.MethodDeclaration,
267+
node: ts.FunctionDeclaration | ts.MethodDeclaration | ts.ArrowFunction,
217268
sourceFile: ts.SourceFile
218269
): Result<IFunctionInfo> | null {
219270
try {
@@ -222,11 +273,12 @@ export class TypeScriptCodeMapper implements ITypeScriptCodeMapper {
222273
}
223274

224275
const name: string = node.name.getText(sourceFile);
225-
const content: string = this.getPrintedNode(node, sourceFile);
276+
const content: string = this.getFunctionNodeText(node, sourceFile);
226277
const parameters: IProperty[] = this.extractFunctionParameters(
227278
node,
228279
sourceFile
229280
).getValue();
281+
230282
const details = this.functionDetailsMapper(
231283
name,
232284
content,
@@ -254,7 +306,7 @@ export class TypeScriptCodeMapper implements ITypeScriptCodeMapper {
254306
name: string,
255307
content: string,
256308
parameters: IProperty[],
257-
node: ts.FunctionDeclaration | ts.MethodDeclaration
309+
node: ts.FunctionDeclaration | ts.MethodDeclaration | ts.ArrowFunction
258310
) {
259311
return {
260312
name,
@@ -272,12 +324,7 @@ export class TypeScriptCodeMapper implements ITypeScriptCodeMapper {
272324
* @returns A string representation of the function or method type, or undefined if type checking is unavailable.
273325
*/
274326
getTypeAtLocation(
275-
node:
276-
| ts.FunctionDeclaration
277-
| ts.MethodDeclaration
278-
| ts.ParameterDeclaration
279-
| ts.PropertyDeclaration
280-
| ts.PropertySignature
327+
node: DeclarationOrFunctionNode
281328
): Result<string | undefined> {
282329
const type = this.typeChecker?.typeToString(
283330
this.typeChecker.getTypeAtLocation(node)
@@ -307,8 +354,8 @@ export class TypeScriptCodeMapper implements ITypeScriptCodeMapper {
307354
* @param sourceFile The source file that contains the node to be printed.
308355
* @returns A string representation of the given node.
309356
*/
310-
getPrintedNode(
311-
node: ts.FunctionDeclaration | ts.MethodDeclaration,
357+
getFunctionNodeText(
358+
node: ts.FunctionDeclaration | ts.MethodDeclaration | ts.ArrowFunction,
312359
sourceFile: ts.SourceFile
313360
) {
314361
const printer: ts.Printer = ts.createPrinter({
@@ -367,6 +414,51 @@ export class TypeScriptCodeMapper implements ITypeScriptCodeMapper {
367414
};
368415
}
369416

417+
/**
418+
* Retrieves a source file from the TypeScript program by its filename.
419+
*
420+
* @param fileName - The path to the source file to retrieve
421+
* @returns The SourceFile object if found, undefined otherwise
422+
*/
423+
getSourceFile(fileName: string): ts.SourceFile | undefined {
424+
return this.program?.getSourceFile(fileName);
425+
}
426+
427+
/**
428+
* Gets an array of all root file names in the TypeScript program.
429+
* Root files are the entry points specified in the tsconfig.json or passed to the compiler.
430+
*
431+
* @returns A readonly array of file paths, or undefined if the program is not initialized
432+
*/
433+
getRootFileNames(): readonly string[] | undefined {
434+
return this.program?.getRootFileNames();
435+
}
436+
437+
/**
438+
* Returns the current TypeScript program instance.
439+
* The program object represents the entire TypeScript project and provides
440+
* access to the compiler's internal state.
441+
*
442+
* @returns The TypeScript Program object, or undefined if not initialized
443+
*/
444+
getProgram(): ts.Program | undefined {
445+
return this.program;
446+
}
447+
448+
/**
449+
* Retrieves the TypeChecker instance from the current program.
450+
* The TypeChecker is responsible for type analysis and provides
451+
* APIs for querying type information.
452+
*
453+
* @returns The TypeScript TypeChecker object, or undefined if the program is not initialized
454+
* @remarks This method creates a new type checker instance each time it's called,
455+
* consider caching the result if multiple calls are needed
456+
*/
457+
getTypeChecker(): ts.TypeChecker | undefined {
458+
const program = this.getProgram();
459+
return program ? program.getTypeChecker() : undefined;
460+
}
461+
370462
/**
371463
* Builds a hierarchical map of the codebase by traversing TypeScript files
372464
* and extracting module and class information.
@@ -380,7 +472,7 @@ export class TypeScriptCodeMapper implements ITypeScriptCodeMapper {
380472
const tsFiles: string[] = await this.getTsFiles();
381473
tsFiles.forEach((filePath) => {
382474
const moduleRalativePath = path.relative(rootDir, filePath);
383-
const sourceFile = this.program?.getSourceFile(filePath);
475+
const sourceFile = this.getSourceFile(filePath);
384476

385477
if (!sourceFile) {
386478
throw Error(`No source file found for ${filePath}`);
@@ -399,8 +491,28 @@ export class TypeScriptCodeMapper implements ITypeScriptCodeMapper {
399491
if (classInfo) {
400492
moduleInfo?.classes?.push(classInfo);
401493
}
494+
this.processClassMembers(node, sourceFile, moduleInfo);
495+
}
496+
497+
if (
498+
ts.isMethodDeclaration(node) ||
499+
ts.isFunctionDeclaration(node) ||
500+
(ts.isVariableDeclaration(node) && ts.isArrowFunction(node))
501+
) {
502+
this.aggregateFunctions(node, sourceFile, moduleInfo);
503+
}
504+
505+
if (ts.isPropertyDeclaration(node)) {
506+
this.aggergateProperties(node, sourceFile, moduleInfo);
507+
}
508+
509+
if (ts.isInterfaceDeclaration(node)) {
510+
this.aggregateInterfaces(node, sourceFile, moduleInfo);
511+
}
512+
513+
if (ts.isEnumDeclaration(node)) {
514+
this.aggregateEnums(node, sourceFile, moduleInfo);
402515
}
403-
this.processClassMembers(node, sourceFile, moduleInfo);
404516
codebaseMap[repoNames].modules[moduleRalativePath] = moduleInfo;
405517
});
406518
});

0 commit comments

Comments
 (0)