Skip to content

Commit

Permalink
feat: Update Next.js codebase scanner to analyze source files
Browse files Browse the repository at this point in the history
- Add new function `analyzeNextjsSourceFiles` to handle the source files
- Update `scanNextjsCodebase` to call `analyzeNextjsSourceFiles` and return the result
- Add tests
- Update imports
  • Loading branch information
ozhanefemeral committed Jul 22, 2024
1 parent 76f0b0f commit cb7aba3
Show file tree
Hide file tree
Showing 5 changed files with 113 additions and 21 deletions.
96 changes: 96 additions & 0 deletions packages/ts-generator/src/nextjs/__tests__/nextjs.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { Project } from "ts-morph";
import {
analyzeNextjsSourceFiles,
generateServerAction,
ServerActionInfo,
} from "../index";

describe("Next.js functionality", () => {
let project: Project;

beforeEach(() => {
project = new Project({ useInMemoryFileSystem: true });
});

describe("analyzeNextjsSourceFiles", () => {
it("should correctly identify and parse server actions", () => {
const serverActionCode = `
"use server";
export async function submitForm(data: FormData) {
// Server action logic
}
export async function deleteItem(id: string) {
// Delete item logic
}
`;

const sourceFile = project.createSourceFile(
"app/actions.ts",
serverActionCode
);

const result = analyzeNextjsSourceFiles([sourceFile]);

expect(result.serverActions).toHaveLength(2);
expect(result.serverActions[0]?.name).toBe("submitForm");
expect(result.serverActions[1]?.name).toBe("deleteItem");
});

it("should not identify non-server actions", () => {
const regularCode = `
export function regularFunction() {
// Regular function logic
}
`;

const sourceFile = project.createSourceFile(
"app/regular.ts",
regularCode
);

const result = analyzeNextjsSourceFiles([sourceFile]);

expect(result.serverActions).toHaveLength(0);
});
});

describe("generateServerAction", () => {
it("should generate correct server action code", () => {
const serverActionInfo: ServerActionInfo = {
name: "testAction",
returnType: "Promise<string>", // This could be 'string' or 'Promise<string>', the function should handle both
parameters: [
{ name: "data", type: "FormData" },
{ name: "userId", type: "string" },
],
filePath: "app/actions.ts",
};

const generatedCode = generateServerAction(serverActionInfo);

expect(generatedCode).toContain('"use server";');
expect(generatedCode).toContain(
"export async function testAction(data: FormData, userId: string): Promise<string>"
);
expect(generatedCode).toContain("// TODO: Implement server action logic");
expect(generatedCode).toContain('throw new Error("Not implemented");');
});

it("should handle non-Promise return types", () => {
const serverActionInfo: ServerActionInfo = {
name: "testAction",
returnType: "void",
parameters: [],
filePath: "app/actions.ts",
};

const generatedCode = generateServerAction(serverActionInfo);

expect(generatedCode).toContain(
"export async function testAction(): Promise<void>"
);
});
});
});
8 changes: 5 additions & 3 deletions packages/ts-generator/src/nextjs/generator.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
// src/nextjs/generator.ts

import { ServerActionInfo } from "./types";

export function generateServerAction(info: ServerActionInfo): string {
const parameters = (info.parameters ?? [])
.map((param) => `${param.name}: ${param.type}`)
.join(", ");

let returnType = info.returnType.replace(/Promise<(.*)>/, "$1");

returnType = `Promise<${returnType}>`;

return `"use server";
export async function ${info.name}(${parameters}): Promise<${info.returnType}> {
export async function ${info.name}(${parameters}): ${returnType} {
// TODO: Implement server action logic
throw new Error("Not implemented");
}`;
Expand Down
8 changes: 1 addition & 7 deletions packages/ts-generator/src/nextjs/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,3 @@
// Named exports for types
export type { NextCodebaseInfo, ServerActionInfo } from "./types";

// Named exports for functions
export { scanNextjsCodebase } from "./scanner";
export { scanNextjsCodebase, analyzeNextjsSourceFiles } from "./scanner";
export { generateServerAction } from "./generator";

// Optionally, you can also include a convenience export
export * as NextJS from "./";
20 changes: 10 additions & 10 deletions packages/ts-generator/src/nextjs/scanner.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
// src/nextjs/scanner.ts

import { Project, SourceFile, Node } from "ts-morph";
import { FunctionInfo } from "../types";
import { Project, SourceFile } from "ts-morph";
import { ServerActionInfo, NextCodebaseInfo } from "./types";

export function scanNextjsCodebase(projectPath: string): NextCodebaseInfo {
const project = new Project();
project.addSourceFilesAtPaths(`${projectPath}/**/*.ts`);
return analyzeNextjsSourceFiles(project.getSourceFiles());
}

export function analyzeNextjsSourceFiles(
sourceFiles: SourceFile[]
): NextCodebaseInfo {
const serverActions: ServerActionInfo[] = [];

project.getSourceFiles().forEach((sourceFile) => {
sourceFiles.forEach((sourceFile) => {
if (isServerActionFile(sourceFile)) {
serverActions.push(...extractServerActions(sourceFile));
}
Expand All @@ -28,19 +30,17 @@ function extractServerActions(sourceFile: SourceFile): ServerActionInfo[] {

sourceFile.getFunctions().forEach((func) => {
if (func.isAsync()) {
const functionInfo: FunctionInfo = {
const functionInfo: ServerActionInfo = {
name: func.getName() || "anonymous",
returnType: func.getReturnType().getText(),
parameters: func.getParameters().map((param) => ({
name: param.getName(),
type: param.getType().getText(),
})),
filePath: sourceFile.getFilePath(),
};

serverActions.push({
...functionInfo,
filePath: sourceFile.getFilePath(),
});
serverActions.push(functionInfo);
}
});

Expand Down
2 changes: 1 addition & 1 deletion packages/ts-generator/src/nextjs/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { FunctionInfo } from "types";
import { FunctionInfo } from "../types";

export interface ServerActionInfo extends FunctionInfo {
filePath: string;
Expand Down

0 comments on commit cb7aba3

Please sign in to comment.