Skip to content

Commit 0d1621f

Browse files
Copilotitsdouges
andcommitted
Add error handling for ts-morph maximum call stack errors
Co-authored-by: itsdouges <[email protected]>
1 parent c0097fb commit 0d1621f

File tree

4 files changed

+85
-2
lines changed

4 files changed

+85
-2
lines changed

packages/@triplex/server/src/ast/__tests__/__mocks__/box.tsx

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,26 @@ export default function Box({
2424
export function UseBox() {
2525
return <Box position={[0, 0, 0]} scale={[1, 1, 1]} />;
2626
}
27+
28+
class Foo { }
29+
30+
class Foo {
31+
}
32+
33+
class Foo {
34+
}
35+
36+
class Foo {
37+
}
38+
39+
function foo() {
40+
}
41+
42+
class Foo {
43+
}
44+
45+
class Foo {
46+
}
47+
48+
function foo() {
49+
}

packages/@triplex/server/src/ast/__tests__/project.test.tsx

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -498,4 +498,37 @@ describe("project ast", () => {
498498

499499
expect(sourceFile.isDirty()).toEqual(true);
500500
});
501+
502+
it("should handle errors during edit gracefully", async () => {
503+
const project = createProject({
504+
cwd: process.cwd(),
505+
templates: { newElements: "" },
506+
tsConfigFilePath: join(__dirname, "__mocks__", "tsconfig.json"),
507+
});
508+
const sourceFile = project.getSourceFile(
509+
join(__dirname, "__mocks__", "box.tsx"),
510+
);
511+
const originalText = sourceFile.read().getFullText();
512+
513+
const [ids, result] = await sourceFile.edit(() => {
514+
// Simulate an error that might occur in ts-morph
515+
throw new Error("Maximum call stack size exceeded");
516+
});
517+
518+
// Should return error status
519+
expect(ids.status).toEqual("error");
520+
expect(ids).toHaveProperty("error");
521+
if ("error" in ids) {
522+
expect(ids.error).toContain("Maximum call stack");
523+
}
524+
525+
// Result should be undefined when error occurs
526+
expect(result).toBeUndefined();
527+
528+
// Source file should be reverted to original state
529+
expect(sourceFile.read().getFullText()).toEqual(originalText);
530+
531+
// Source file should not be marked as dirty
532+
expect(sourceFile.isDirty()).toEqual(false);
533+
});
501534
});

packages/@triplex/server/src/ast/project.ts

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -291,7 +291,33 @@ export function ${componentName}() {
291291
];
292292
let undoStackPointer = sourceFileHistoryPointer.get(sourceFile) || 0;
293293

294-
const result = await callback(sourceFile);
294+
let result: TResult extends SourceFile ? never : TResult;
295+
296+
try {
297+
result = await callback(sourceFile);
298+
} catch (error) {
299+
// Handle errors from ts-morph gracefully. These can occur when TypeScript
300+
// encounters issues like circular imports or maximum call stack errors during
301+
// import resolution. We log the error and return an error status to allow
302+
// Triplex to continue working instead of crashing.
303+
const err = error as Error;
304+
// eslint-disable-next-line no-console
305+
console.error(
306+
"Error during source file manipulation:",
307+
err.message,
308+
);
309+
310+
// Reset the source file to its original state to maintain consistency
311+
sourceFile.replaceWithText(currentFullText);
312+
313+
return [
314+
{
315+
error: err.message,
316+
status: "error",
317+
},
318+
undefined as TResult extends SourceFile ? never : TResult,
319+
];
320+
}
295321

296322
const nextFullText = sourceFile.getFullText();
297323

packages/@triplex/server/src/types.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ export type Source = "react" | "three" | "unknown";
99

1010
export type Mutation =
1111
| { redoID: number; status: "modified"; undoID: number }
12-
| { status: "unmodified" };
12+
| { status: "unmodified" }
13+
| { error: string; status: "error" };
1314

1415
export interface Transforms {
1516
rotate: boolean;

0 commit comments

Comments
 (0)