Build code introspection service (#7760)
Starting to use ts-morph to retrieve function parameters
This commit is contained in:
@ -44,6 +44,7 @@
|
|||||||
"monaco-editor-auto-typings": "^0.4.5",
|
"monaco-editor-auto-typings": "^0.4.5",
|
||||||
"passport": "^0.7.0",
|
"passport": "^0.7.0",
|
||||||
"psl": "^1.9.0",
|
"psl": "^1.9.0",
|
||||||
|
"ts-morph": "^24.0.0",
|
||||||
"tsconfig-paths": "^4.2.0",
|
"tsconfig-paths": "^4.2.0",
|
||||||
"typeorm": "patch:typeorm@0.3.20#./patches/typeorm+0.3.20.patch",
|
"typeorm": "patch:typeorm@0.3.20#./patches/typeorm+0.3.20.patch",
|
||||||
"unzipper": "^0.12.3",
|
"unzipper": "^0.12.3",
|
||||||
|
|||||||
@ -4,19 +4,24 @@ import { InjectRepository } from '@nestjs/typeorm';
|
|||||||
import { basename, dirname, join } from 'path';
|
import { basename, dirname, join } from 'path';
|
||||||
|
|
||||||
import { TypeOrmQueryService } from '@ptc-org/nestjs-query-typeorm';
|
import { TypeOrmQueryService } from '@ptc-org/nestjs-query-typeorm';
|
||||||
import { Repository } from 'typeorm';
|
|
||||||
import deepEqual from 'deep-equal';
|
import deepEqual from 'deep-equal';
|
||||||
|
import { Repository } from 'typeorm';
|
||||||
|
|
||||||
import { FileStorageExceptionCode } from 'src/engine/core-modules/file-storage/interfaces/file-storage-exception';
|
import { FileStorageExceptionCode } from 'src/engine/core-modules/file-storage/interfaces/file-storage-exception';
|
||||||
import { ServerlessExecuteResult } from 'src/engine/core-modules/serverless/drivers/interfaces/serverless-driver.interface';
|
import { ServerlessExecuteResult } from 'src/engine/core-modules/serverless/drivers/interfaces/serverless-driver.interface';
|
||||||
|
|
||||||
import { ThrottlerService } from 'src/engine/core-modules/throttler/throttler.service';
|
|
||||||
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
|
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
|
||||||
import { FileStorageService } from 'src/engine/core-modules/file-storage/file-storage.service';
|
import { FileStorageService } from 'src/engine/core-modules/file-storage/file-storage.service';
|
||||||
import { readFileContent } from 'src/engine/core-modules/file-storage/utils/read-file-content';
|
import { readFileContent } from 'src/engine/core-modules/file-storage/utils/read-file-content';
|
||||||
|
import { ENV_FILE_NAME } from 'src/engine/core-modules/serverless/drivers/constants/env-file-name';
|
||||||
import { INDEX_FILE_NAME } from 'src/engine/core-modules/serverless/drivers/constants/index-file-name';
|
import { INDEX_FILE_NAME } from 'src/engine/core-modules/serverless/drivers/constants/index-file-name';
|
||||||
|
import { LAST_LAYER_VERSION } from 'src/engine/core-modules/serverless/drivers/layers/last-layer-version';
|
||||||
|
import { getBaseTypescriptProjectFiles } from 'src/engine/core-modules/serverless/drivers/utils/get-base-typescript-project-files';
|
||||||
|
import { getLastLayerDependencies } from 'src/engine/core-modules/serverless/drivers/utils/get-last-layer-dependencies';
|
||||||
import { ServerlessService } from 'src/engine/core-modules/serverless/serverless.service';
|
import { ServerlessService } from 'src/engine/core-modules/serverless/serverless.service';
|
||||||
import { getServerlessFolder } from 'src/engine/core-modules/serverless/utils/serverless-get-folder.utils';
|
import { getServerlessFolder } from 'src/engine/core-modules/serverless/utils/serverless-get-folder.utils';
|
||||||
|
import { ThrottlerService } from 'src/engine/core-modules/throttler/throttler.service';
|
||||||
|
import { CreateServerlessFunctionInput } from 'src/engine/metadata-modules/serverless-function/dtos/create-serverless-function.input';
|
||||||
import { UpdateServerlessFunctionInput } from 'src/engine/metadata-modules/serverless-function/dtos/update-serverless-function.input';
|
import { UpdateServerlessFunctionInput } from 'src/engine/metadata-modules/serverless-function/dtos/update-serverless-function.input';
|
||||||
import {
|
import {
|
||||||
ServerlessFunctionEntity,
|
ServerlessFunctionEntity,
|
||||||
@ -27,11 +32,6 @@ import {
|
|||||||
ServerlessFunctionExceptionCode,
|
ServerlessFunctionExceptionCode,
|
||||||
} from 'src/engine/metadata-modules/serverless-function/serverless-function.exception';
|
} from 'src/engine/metadata-modules/serverless-function/serverless-function.exception';
|
||||||
import { isDefined } from 'src/utils/is-defined';
|
import { isDefined } from 'src/utils/is-defined';
|
||||||
import { getLastLayerDependencies } from 'src/engine/core-modules/serverless/drivers/utils/get-last-layer-dependencies';
|
|
||||||
import { LAST_LAYER_VERSION } from 'src/engine/core-modules/serverless/drivers/layers/last-layer-version';
|
|
||||||
import { CreateServerlessFunctionInput } from 'src/engine/metadata-modules/serverless-function/dtos/create-serverless-function.input';
|
|
||||||
import { getBaseTypescriptProjectFiles } from 'src/engine/core-modules/serverless/drivers/utils/get-base-typescript-project-files';
|
|
||||||
import { ENV_FILE_NAME } from 'src/engine/core-modules/serverless/drivers/constants/env-file-name';
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ServerlessFunctionService extends TypeOrmQueryService<ServerlessFunctionEntity> {
|
export class ServerlessFunctionService extends TypeOrmQueryService<ServerlessFunctionEntity> {
|
||||||
|
|||||||
@ -0,0 +1,106 @@
|
|||||||
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
|
||||||
|
import { CodeIntrospectionException } from 'src/modules/code-introspection/code-introspection.exception';
|
||||||
|
import { CodeIntrospectionService } from 'src/modules/code-introspection/code-introspection.service';
|
||||||
|
|
||||||
|
describe('CodeIntrospectionService', () => {
|
||||||
|
let service: CodeIntrospectionService;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
|
providers: [CodeIntrospectionService],
|
||||||
|
}).compile();
|
||||||
|
|
||||||
|
service = module.get<CodeIntrospectionService>(CodeIntrospectionService);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be defined', () => {
|
||||||
|
expect(service).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('analyze', () => {
|
||||||
|
it('should analyze a function declaration correctly', () => {
|
||||||
|
const fileContent = `
|
||||||
|
function testFunction(param1: string, param2: number): void {
|
||||||
|
console.log(param1, param2);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const result = service.analyze(fileContent);
|
||||||
|
|
||||||
|
expect(result).toEqual([
|
||||||
|
{ name: 'param1', type: 'string' },
|
||||||
|
{ name: 'param2', type: 'number' },
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should analyze an arrow function correctly', () => {
|
||||||
|
const fileContent = `
|
||||||
|
const testArrowFunction = (param1: string, param2: number): void => {
|
||||||
|
console.log(param1, param2);
|
||||||
|
};
|
||||||
|
`;
|
||||||
|
|
||||||
|
const result = service.analyze(fileContent);
|
||||||
|
|
||||||
|
expect(result).toEqual([
|
||||||
|
{ name: 'param1', type: 'string' },
|
||||||
|
{ name: 'param2', type: 'number' },
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return an empty array for files without functions', () => {
|
||||||
|
const fileContent = `
|
||||||
|
const x = 5;
|
||||||
|
console.log(x);
|
||||||
|
`;
|
||||||
|
|
||||||
|
const result = service.analyze(fileContent);
|
||||||
|
|
||||||
|
expect(result).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw an exception for multiple function declarations', () => {
|
||||||
|
const fileContent = `
|
||||||
|
function func1(param1: string) {}
|
||||||
|
function func2(param2: number) {}
|
||||||
|
`;
|
||||||
|
|
||||||
|
expect(() => service.analyze(fileContent)).toThrow(
|
||||||
|
CodeIntrospectionException,
|
||||||
|
);
|
||||||
|
expect(() => service.analyze(fileContent)).toThrow(
|
||||||
|
'Only one function is allowed',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw an exception for multiple arrow functions', () => {
|
||||||
|
const fileContent = `
|
||||||
|
const func1 = (param1: string) => {};
|
||||||
|
const func2 = (param2: number) => {};
|
||||||
|
`;
|
||||||
|
|
||||||
|
expect(() => service.analyze(fileContent)).toThrow(
|
||||||
|
CodeIntrospectionException,
|
||||||
|
);
|
||||||
|
expect(() => service.analyze(fileContent)).toThrow(
|
||||||
|
'Only one arrow function is allowed',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should correctly analyze complex types', () => {
|
||||||
|
const fileContent = `
|
||||||
|
function complexFunction(param1: string[], param2: { key: number }): Promise<boolean> {
|
||||||
|
return Promise.resolve(true);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const result = service.analyze(fileContent);
|
||||||
|
|
||||||
|
expect(result).toEqual([
|
||||||
|
{ name: 'param1', type: 'string[]' },
|
||||||
|
{ name: 'param2', type: '{ key: number; }' },
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
import { CustomException } from 'src/utils/custom-exception';
|
||||||
|
|
||||||
|
export class CodeIntrospectionException extends CustomException {
|
||||||
|
code: CodeIntrospectionExceptionCode;
|
||||||
|
constructor(message: string, code: CodeIntrospectionExceptionCode) {
|
||||||
|
super(message, code);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum CodeIntrospectionExceptionCode {
|
||||||
|
ONLY_ONE_FUNCTION_ALLOWED = 'ONLY_ONE_FUNCTION_ALLOWED',
|
||||||
|
}
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { CodeIntrospectionService } from 'src/modules/code-introspection/code-introspection.service';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
providers: [CodeIntrospectionService],
|
||||||
|
exports: [CodeIntrospectionService],
|
||||||
|
})
|
||||||
|
export class CodeIntrospectionModule {}
|
||||||
@ -0,0 +1,92 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
|
import {
|
||||||
|
ArrowFunction,
|
||||||
|
FunctionDeclaration,
|
||||||
|
ParameterDeclaration,
|
||||||
|
Project,
|
||||||
|
SyntaxKind,
|
||||||
|
} from 'ts-morph';
|
||||||
|
|
||||||
|
import {
|
||||||
|
CodeIntrospectionException,
|
||||||
|
CodeIntrospectionExceptionCode,
|
||||||
|
} from 'src/modules/code-introspection/code-introspection.exception';
|
||||||
|
|
||||||
|
type FunctionParameter = {
|
||||||
|
name: string;
|
||||||
|
type: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class CodeIntrospectionService {
|
||||||
|
private project: Project;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.project = new Project();
|
||||||
|
}
|
||||||
|
|
||||||
|
public analyze(
|
||||||
|
fileContent: string,
|
||||||
|
fileName = 'temp.ts',
|
||||||
|
): FunctionParameter[] {
|
||||||
|
const sourceFile = this.project.createSourceFile(fileName, fileContent, {
|
||||||
|
overwrite: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const functionDeclarations = sourceFile.getFunctions();
|
||||||
|
|
||||||
|
if (functionDeclarations.length > 0) {
|
||||||
|
return this.analyzeFunctions(functionDeclarations);
|
||||||
|
}
|
||||||
|
|
||||||
|
const arrowFunctions = sourceFile.getDescendantsOfKind(
|
||||||
|
SyntaxKind.ArrowFunction,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (arrowFunctions.length > 0) {
|
||||||
|
return this.analyzeArrowFunctions(arrowFunctions);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
private analyzeFunctions(
|
||||||
|
functionDeclarations: FunctionDeclaration[],
|
||||||
|
): FunctionParameter[] {
|
||||||
|
if (functionDeclarations.length > 1) {
|
||||||
|
throw new CodeIntrospectionException(
|
||||||
|
'Only one function is allowed',
|
||||||
|
CodeIntrospectionExceptionCode.ONLY_ONE_FUNCTION_ALLOWED,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const functionDeclaration = functionDeclarations[0];
|
||||||
|
|
||||||
|
return functionDeclaration.getParameters().map(this.buildFunctionParameter);
|
||||||
|
}
|
||||||
|
|
||||||
|
private analyzeArrowFunctions(
|
||||||
|
arrowFunctions: ArrowFunction[],
|
||||||
|
): FunctionParameter[] {
|
||||||
|
if (arrowFunctions.length > 1) {
|
||||||
|
throw new CodeIntrospectionException(
|
||||||
|
'Only one arrow function is allowed',
|
||||||
|
CodeIntrospectionExceptionCode.ONLY_ONE_FUNCTION_ALLOWED,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const arrowFunction = arrowFunctions[0];
|
||||||
|
|
||||||
|
return arrowFunction.getParameters().map(this.buildFunctionParameter);
|
||||||
|
}
|
||||||
|
|
||||||
|
private buildFunctionParameter(
|
||||||
|
parameter: ParameterDeclaration,
|
||||||
|
): FunctionParameter {
|
||||||
|
return {
|
||||||
|
name: parameter.getName(),
|
||||||
|
type: parameter.getType().getText(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
51
yarn.lock
51
yarn.lock
@ -15073,6 +15073,17 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@ts-morph/common@npm:~0.25.0":
|
||||||
|
version: 0.25.0
|
||||||
|
resolution: "@ts-morph/common@npm:0.25.0"
|
||||||
|
dependencies:
|
||||||
|
minimatch: "npm:^9.0.4"
|
||||||
|
path-browserify: "npm:^1.0.1"
|
||||||
|
tinyglobby: "npm:^0.2.9"
|
||||||
|
checksum: 10c0/c67e66db678e44886e9823e6482834acebfae0ea52ccbfa2af1ca9abfe5a9774dad6e852c8f480909bc196175f17e15454af71d7a41a1c137db09e74f046a830
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@tsconfig/node10@npm:^1.0.7":
|
"@tsconfig/node10@npm:^1.0.7":
|
||||||
version: 1.0.11
|
version: 1.0.11
|
||||||
resolution: "@tsconfig/node10@npm:1.0.11"
|
resolution: "@tsconfig/node10@npm:1.0.11"
|
||||||
@ -22021,6 +22032,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"code-block-writer@npm:^13.0.3":
|
||||||
|
version: 13.0.3
|
||||||
|
resolution: "code-block-writer@npm:13.0.3"
|
||||||
|
checksum: 10c0/87db97b37583f71cfd7eced8bf3f0a0a0ca53af912751a734372b36c08cd27f3e8a4878ec05591c0cd9ae11bea8add1423e132d660edd86aab952656dd41fd66
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"code-point-at@npm:^1.0.0":
|
"code-point-at@npm:^1.0.0":
|
||||||
version: 1.1.0
|
version: 1.1.0
|
||||||
resolution: "code-point-at@npm:1.1.0"
|
resolution: "code-point-at@npm:1.1.0"
|
||||||
@ -26491,6 +26509,18 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"fdir@npm:^6.4.0":
|
||||||
|
version: 6.4.0
|
||||||
|
resolution: "fdir@npm:6.4.0"
|
||||||
|
peerDependencies:
|
||||||
|
picomatch: ^3 || ^4
|
||||||
|
peerDependenciesMeta:
|
||||||
|
picomatch:
|
||||||
|
optional: true
|
||||||
|
checksum: 10c0/9a03efa1335d78ea386b701799b08ad9e7e8da85d88567dc162cd28dd8e9486e8c269b3e95bfeb21dd6a5b14ebf69d230eb6e18f49d33fbda3cd97432f648c48
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"fetch-retry@npm:^5.0.2":
|
"fetch-retry@npm:^5.0.2":
|
||||||
version: 5.0.6
|
version: 5.0.6
|
||||||
resolution: "fetch-retry@npm:5.0.6"
|
resolution: "fetch-retry@npm:5.0.6"
|
||||||
@ -43048,6 +43078,16 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"tinyglobby@npm:^0.2.9":
|
||||||
|
version: 0.2.9
|
||||||
|
resolution: "tinyglobby@npm:0.2.9"
|
||||||
|
dependencies:
|
||||||
|
fdir: "npm:^6.4.0"
|
||||||
|
picomatch: "npm:^4.0.2"
|
||||||
|
checksum: 10c0/f65f847afe70f56de069d4f1f9c3b0c1a76aaf2b0297656754734a83b9bac8e105b5534dfbea8599560476b88f7b747d0855370a957a07246d18b976addb87ec
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"tinypool@npm:^0.8.2":
|
"tinypool@npm:^0.8.2":
|
||||||
version: 0.8.4
|
version: 0.8.4
|
||||||
resolution: "tinypool@npm:0.8.4"
|
resolution: "tinypool@npm:0.8.4"
|
||||||
@ -43474,6 +43514,16 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"ts-morph@npm:^24.0.0":
|
||||||
|
version: 24.0.0
|
||||||
|
resolution: "ts-morph@npm:24.0.0"
|
||||||
|
dependencies:
|
||||||
|
"@ts-morph/common": "npm:~0.25.0"
|
||||||
|
code-block-writer: "npm:^13.0.3"
|
||||||
|
checksum: 10c0/2a0813ba428a154966d4038901f6c32457a60870936b23778f2629433257f87d1881fc4ecae7b791a223a88c2edf96aaac9fb0f88bf34d3c652af8c09c4f43bc
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"ts-node@npm:10.9.1":
|
"ts-node@npm:10.9.1":
|
||||||
version: 10.9.1
|
version: 10.9.1
|
||||||
resolution: "ts-node@npm:10.9.1"
|
resolution: "ts-node@npm:10.9.1"
|
||||||
@ -43817,6 +43867,7 @@ __metadata:
|
|||||||
passport: "npm:^0.7.0"
|
passport: "npm:^0.7.0"
|
||||||
psl: "npm:^1.9.0"
|
psl: "npm:^1.9.0"
|
||||||
rimraf: "npm:^5.0.5"
|
rimraf: "npm:^5.0.5"
|
||||||
|
ts-morph: "npm:^24.0.0"
|
||||||
tsconfig-paths: "npm:^4.2.0"
|
tsconfig-paths: "npm:^4.2.0"
|
||||||
typeorm: "patch:typeorm@0.3.20#./patches/typeorm+0.3.20.patch"
|
typeorm: "patch:typeorm@0.3.20#./patches/typeorm+0.3.20.patch"
|
||||||
typescript: "npm:5.3.3"
|
typescript: "npm:5.3.3"
|
||||||
|
|||||||
Reference in New Issue
Block a user