diff --git a/.vscode/settings.json b/.vscode/settings.json index f9c9da5..39a872d 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -2,5 +2,8 @@ "deno.enable": true, "editor.defaultFormatter": "denoland.vscode-deno", "prettier.enable": false, - "biome.enabled": false + "biome.enabled": false, + "cSpell.words": [ + "tsec" + ] } diff --git a/README.md b/README.md new file mode 100644 index 0000000..f3cebd6 --- /dev/null +++ b/README.md @@ -0,0 +1,9 @@ +# TypeScript Erasable Codemod + +Hi! This is `tsec`, the codemod tool for automatically migrating your code to be +compatible with TypeScript's +[erasableSyntaxOnly](https://www.typescriptlang.org/tsconfig/#erasableSyntaxOnly) +flag. + +The assumptions and opinions related to the changes that will be made are +described in [assumptions.md](./docs/assumptions.md). diff --git a/assumptions.md b/docs/assumptions.md similarity index 100% rename from assumptions.md rename to docs/assumptions.md diff --git a/main.ts b/src/enum/enum.ts similarity index 100% rename from main.ts rename to src/enum/enum.ts diff --git a/main_test.ts b/src/enum/enum_test.ts similarity index 75% rename from main_test.ts rename to src/enum/enum_test.ts index 3c19f7f..9480e42 100644 --- a/main_test.ts +++ b/src/enum/enum_test.ts @@ -1,4 +1,4 @@ -import { runCodemod } from "./main.ts"; +import { runCodemod } from "./enum.ts"; import { spawn } from "node:child_process"; import { assertEquals } from "jsr:@std/assert"; @@ -569,68 +569,12 @@ Deno.test("preserves comments", async () => { const testFile = "comments_test.ts"; try { - const originalContent = `/** - * Represents different user roles in the system - */ -enum UserRole { - /** Regular user with basic permissions */ - User = "user", - /** Moderator with elevated permissions */ - Moderator = "moderator", - /** Administrator with full access */ - Admin = "admin" -} - -// Default role for new users -const DEFAULT_ROLE: UserRole = UserRole.User; - -/* - * Permission check function - */ -function hasPermission(role: UserRole, action: string): boolean { - switch (role) { - case UserRole.Admin: - return true; // Admin can do everything - case UserRole.Moderator: - return action !== "delete_user"; - case UserRole.User: - return action === "read"; - default: - return false; - } -}`; - - const expectedOutput = `/** - * Represents different user roles in the system - */ -const UserRole = { - /** Regular user with basic permissions */ - User: "user", - /** Moderator with elevated permissions */ - Moderator: "moderator", - /** Administrator with full access */ - Admin: "admin", -} as const; -type UserRoleType = typeof UserRole[keyof typeof UserRole]; - -// Default role for new users -const DEFAULT_ROLE: UserRoleType = UserRole.User; - -/* - * Permission check function - */ -function hasPermission(role: UserRoleType, action: string): boolean { - switch (role) { - case UserRole.Admin: - return true; // Admin can do everything - case UserRole.Moderator: - return action !== "delete_user"; - case UserRole.User: - return action === "read"; - default: - return false; - } -}`; + const originalContent = await Deno.readTextFile( + "src/enum/spec/comments.in.ts", + ); + const expectedOutput = await Deno.readTextFile( + "src/enum/spec/comments.out.ts", + ); await utils.createTestFile(testFile, originalContent); const result = await utils.runAndReadCodemod(testFile); @@ -641,56 +585,23 @@ function hasPermission(role: UserRoleType, action: string): boolean { } }); -// TODO enable if computed needs to be supported -Deno.test.ignore("handles edge case with computed enum values", async () => { - const utils = new TestUtils(); - const testFile = "computed_enum_test.ts"; - - try { - const originalContent = `enum FileSize { - Small = 1024, - Medium = Small * 10, - Large = Medium * 10, - XLarge = Large * 10 -} - -enum Flags { - None = 0, - Read = 1 << 0, - Write = 1 << 1, - Execute = 1 << 2, - All = Read | Write | Execute -} - -function checkFileSize(size: number): string { - if (size <= FileSize.Small) return "small"; - if (size <= FileSize.Medium) return "medium"; - if (size <= FileSize.Large) return "large"; - return "xlarge"; -}`; - - await utils.createTestFile(testFile, originalContent); - const result = await utils.runAndReadCodemod(testFile); - console.log(result); - await checkTsFile(testFile); - assertEquals(result, "todo"); - } finally { - await utils.cleanup(); - } -}); - Deno.test("comprehensive enum transformation - full sample", async () => { const utils = new TestUtils(); const testFile = "comprehensive_sample.ts"; try { - const sampleContent = await Deno.readTextFile("spec/comprehensive.in.ts"); + const sampleContent = await Deno.readTextFile( + "src/enum/spec/comprehensive.in.ts", + ); await utils.createTestFile(testFile, sampleContent); const result = await utils.runAndReadCodemod(testFile); await checkTsFile(testFile); await Deno.writeTextFile("tmp/out.ts", result); - assertEquals(result, await Deno.readTextFile("spec/comprehensive.out.ts")); + assertEquals( + result, + await Deno.readTextFile("src/enum/spec/comprehensive.out.ts"), + ); } finally { await utils.cleanup(); } @@ -704,10 +615,10 @@ Deno.test( try { const originalContent = await Deno.readTextFile( - "spec/object-patterns.in.ts", + "src/enum/spec/object-patterns.in.ts", ); const expectedOutput = await Deno.readTextFile( - "spec/object-patterns.out.ts", + "src/enum/spec/object-patterns.out.ts", ); await utils.createTestFile(testFile, originalContent); @@ -728,10 +639,10 @@ Deno.test( try { const originalContent = await Deno.readTextFile( - "spec/template-literal.in.ts", + "src/enum/spec/template-literal.in.ts", ); const expectedOutput = await Deno.readTextFile( - "spec/template-literal.out.ts", + "src/enum/spec/template-literal.out.ts", ); await utils.createTestFile(testFile, originalContent); @@ -744,86 +655,6 @@ Deno.test( }, ); -Deno.test.ignore("enum usage with generics and type constraints", async () => { - const utils = new TestUtils(); - const testFile = "generics_test.ts"; - - try { - const originalContent = `enum EntityType { - User = "user", - Group = "group", - Organization = "org" -} - -enum Permission { - Read = 1, - Write = 2, - Delete = 4, - Admin = 8 -} - -interface Entity { - id: string; - type: T; - name: string; - permissions: Permission[]; -} - -type UserEntity = Entity; -type GroupEntity = Entity; - -class EntityManager { - private entities: Map> = new Map(); - - create(type: K, id: string, name: string): Entity { - const entity: Entity = { - id, - type, - name, - permissions: [Permission.Read] - }; - - this.entities.set(id, entity as Entity); - return entity; - } - - hasPermission(id: string, permission: Permission): boolean { - const entity = this.entities.get(id); - return entity?.permissions.includes(permission) ?? false; - } - - grantPermission(id: string, permission: Permission): void { - const entity = this.entities.get(id); - if (entity && !entity.permissions.includes(permission)) { - entity.permissions.push(permission); - } - } -} - -function createUserManager(): EntityManager { - return new EntityManager(); -} - -const userManager = createUserManager(); -const user = userManager.create(EntityType.User, "u1", "John Doe"); - -userManager.grantPermission("u1", Permission.Write | Permission.Delete); - -function checkEntityType( - entity: Entity -): entity is Entity { - return entity.type === EntityType.User; -}`; - - await utils.createTestFile(testFile, originalContent); - const result = await utils.runAndReadCodemod(testFile); - await checkTsFile(testFile); - assertEquals(result, "todo"); - } finally { - await utils.cleanup(); - } -}); - Deno.test( "enum usage with arrays, maps and complex data structures", async () => { @@ -832,11 +663,11 @@ Deno.test( try { const originalContent = await Deno.readTextFile( - "spec/data-structures.in.ts", + "src/enum/spec/data-structures.in.ts", ); const expectedOutput = await Deno.readTextFile( - "spec/data-structures.out.ts", + "src/enum/spec/data-structures.out.ts", ); await utils.createTestFile(testFile, originalContent); diff --git a/src/enum/spec/comments.in.ts b/src/enum/spec/comments.in.ts new file mode 100644 index 0000000..cd36091 --- /dev/null +++ b/src/enum/spec/comments.in.ts @@ -0,0 +1,30 @@ +/** + * Represents different user roles in the system + */ +enum UserRole { + /** Regular user with basic permissions */ + User = "user", + /** Moderator with elevated permissions */ + Moderator = "moderator", + /** Administrator with full access */ + Admin = "admin" +} + +// Default role for new users +const DEFAULT_ROLE: UserRole = UserRole.User; + +/* +* Permission check function +*/ +function hasPermission(role: UserRole, action: string): boolean { + switch (role) { + case UserRole.Admin: + return true; // Admin can do everything + case UserRole.Moderator: + return action !== "delete_user"; + case UserRole.User: + return action === "read"; + default: + return false; + } +} \ No newline at end of file diff --git a/src/enum/spec/comments.out.ts b/src/enum/spec/comments.out.ts new file mode 100644 index 0000000..af4ed04 --- /dev/null +++ b/src/enum/spec/comments.out.ts @@ -0,0 +1,31 @@ +/** + * Represents different user roles in the system + */ +const UserRole = { + /** Regular user with basic permissions */ + User: "user", + /** Moderator with elevated permissions */ + Moderator: "moderator", + /** Administrator with full access */ + Admin: "admin", +} as const; +type UserRoleType = typeof UserRole[keyof typeof UserRole]; + +// Default role for new users +const DEFAULT_ROLE: UserRoleType = UserRole.User; + +/* +* Permission check function +*/ +function hasPermission(role: UserRoleType, action: string): boolean { + switch (role) { + case UserRole.Admin: + return true; // Admin can do everything + case UserRole.Moderator: + return action !== "delete_user"; + case UserRole.User: + return action === "read"; + default: + return false; + } +} \ No newline at end of file diff --git a/spec/comprehensive.in.ts b/src/enum/spec/comprehensive.in.ts similarity index 100% rename from spec/comprehensive.in.ts rename to src/enum/spec/comprehensive.in.ts diff --git a/spec/comprehensive.out.ts b/src/enum/spec/comprehensive.out.ts similarity index 100% rename from spec/comprehensive.out.ts rename to src/enum/spec/comprehensive.out.ts diff --git a/spec/data-structures.in.ts b/src/enum/spec/data-structures.in.ts similarity index 100% rename from spec/data-structures.in.ts rename to src/enum/spec/data-structures.in.ts diff --git a/spec/data-structures.out.ts b/src/enum/spec/data-structures.out.ts similarity index 100% rename from spec/data-structures.out.ts rename to src/enum/spec/data-structures.out.ts diff --git a/spec/object-patterns.in.ts b/src/enum/spec/object-patterns.in.ts similarity index 100% rename from spec/object-patterns.in.ts rename to src/enum/spec/object-patterns.in.ts diff --git a/spec/object-patterns.out.ts b/src/enum/spec/object-patterns.out.ts similarity index 100% rename from spec/object-patterns.out.ts rename to src/enum/spec/object-patterns.out.ts diff --git a/spec/template-literal.in.ts b/src/enum/spec/template-literal.in.ts similarity index 100% rename from spec/template-literal.in.ts rename to src/enum/spec/template-literal.in.ts diff --git a/spec/template-literal.out.ts b/src/enum/spec/template-literal.out.ts similarity index 100% rename from spec/template-literal.out.ts rename to src/enum/spec/template-literal.out.ts