Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Build code introspection service #7760

Merged
merged 3 commits into from
Oct 17, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/twenty-server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
"monaco-editor-auto-typings": "^0.4.5",
"passport": "^0.7.0",
"psl": "^1.9.0",
"ts-morph": "^24.0.0",
"tsconfig-paths": "^4.2.0",
"typeorm": "patch:[email protected]#./patches/typeorm+0.3.20.patch",
"unzipper": "^0.12.3",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,24 @@ import { InjectRepository } from '@nestjs/typeorm';
import { basename, dirname, join } from 'path';

import { TypeOrmQueryService } from '@ptc-org/nestjs-query-typeorm';
import { Repository } from 'typeorm';
import deepEqual from 'deep-equal';
import { Repository } from 'typeorm';
thomtrp marked this conversation as resolved.
Show resolved Hide resolved

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 { ThrottlerService } from 'src/engine/core-modules/throttler/throttler.service';
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.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 { 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 { 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 { 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 {
ServerlessFunctionEntity,
Expand All @@ -27,11 +32,6 @@ import {
ServerlessFunctionExceptionCode,
} from 'src/engine/metadata-modules/serverless-function/serverless-function.exception';
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()
export class ServerlessFunctionService extends TypeOrmQueryService<ServerlessFunctionEntity> {
Expand Down
Original file line number Diff line number Diff line change
@@ -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; }' },
]);
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { CustomException } from 'src/utils/custom-exception';

export class CodeIntrospectionException extends CustomException {
code: CodeIntrospectionExceptionCode;
thomtrp marked this conversation as resolved.
Show resolved Hide resolved
constructor(message: string, code: CodeIntrospectionExceptionCode) {
super(message, code);
}
}

export enum CodeIntrospectionExceptionCode {
ONLY_ONE_FUNCTION_ALLOWED = 'ONLY_ONE_FUNCTION_ALLOWED',
}
thomtrp marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -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 {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { Injectable } from '@nestjs/common';

import {
ArrowFunction,
FunctionDeclaration,
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((parameter) => {
return {
name: parameter.getName(),
type: parameter.getType().getText(),
};
});
thomtrp marked this conversation as resolved.
Show resolved Hide resolved
}

private analyzeArrowFunctions(arrowFunctions: ArrowFunction[]) {
thomtrp marked this conversation as resolved.
Show resolved Hide resolved
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((parameter) => {
return {
name: parameter.getName(),
type: parameter.getType().getText(),
};
});
}
}
51 changes: 51 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -15073,6 +15073,17 @@ __metadata:
languageName: node
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":
version: 1.0.11
resolution: "@tsconfig/node10@npm:1.0.11"
Expand Down Expand Up @@ -22021,6 +22032,13 @@ __metadata:
languageName: node
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":
version: 1.1.0
resolution: "code-point-at@npm:1.1.0"
Expand Down Expand Up @@ -26491,6 +26509,18 @@ __metadata:
languageName: node
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":
version: 5.0.6
resolution: "fetch-retry@npm:5.0.6"
Expand Down Expand Up @@ -43048,6 +43078,16 @@ __metadata:
languageName: node
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":
version: 0.8.4
resolution: "tinypool@npm:0.8.4"
Expand Down Expand Up @@ -43474,6 +43514,16 @@ __metadata:
languageName: node
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":
version: 10.9.1
resolution: "ts-node@npm:10.9.1"
Expand Down Expand Up @@ -43817,6 +43867,7 @@ __metadata:
passport: "npm:^0.7.0"
psl: "npm:^1.9.0"
rimraf: "npm:^5.0.5"
ts-morph: "npm:^24.0.0"
tsconfig-paths: "npm:^4.2.0"
typeorm: "patch:[email protected]#./patches/typeorm+0.3.20.patch"
typescript: "npm:5.3.3"
Expand Down
Loading