diff --git a/debug_union.ts b/debug_union.ts new file mode 100644 index 0000000..a9d61fe --- /dev/null +++ b/debug_union.ts @@ -0,0 +1,47 @@ +import { Project, SyntaxKind } from "ts-morph"; + +const project = new Project(); +const sourceFile = project.addSourceFileAtPath("sample_backup.ts"); + +// Find all union types +const unionTypes = sourceFile.getDescendantsOfKind(SyntaxKind.UnionType); + +console.log(`Found ${unionTypes.length} union types`); + +unionTypes.forEach((unionType, index) => { + console.log(`\nUnion type ${index + 1}:`); + console.log(`Text: ${unionType.getText()}`); + + const types = unionType.getTypeNodes(); + console.log(`Number of type nodes: ${types.length}`); + + types.forEach((typeNode, typeIndex) => { + console.log(` Type ${typeIndex + 1}:`); + console.log(` Kind: ${typeNode.getKind()}`); + console.log(` Text: ${typeNode.getText()}`); + + if (typeNode.getKind() === SyntaxKind.PropertyAccessExpression) { + console.log(` Is PropertyAccessExpression: true`); + const propAccess = typeNode as any; + const expression = propAccess.getExpression(); + console.log(` Expression kind: ${expression.getKind()}`); + console.log(` Expression text: ${expression.getText()}`); + console.log(` Property name: ${propAccess.getNameNode().getText()}`); + } + }); +}); + +// Also check for type aliases that might contain union types +const typeAliases = sourceFile.getTypeAliases(); +console.log(`\nFound ${typeAliases.length} type aliases`); + +typeAliases.forEach((typeAlias, index) => { + console.log(`\nType alias ${index + 1}: ${typeAlias.getName()}`); + console.log(`Text: ${typeAlias.getText()}`); + + const typeNode = typeAlias.getTypeNode(); + if (typeNode) { + console.log(`Type node kind: ${typeNode.getKind()}`); + console.log(`Type node text: ${typeNode.getText()}`); + } +}); diff --git a/main.ts b/main.ts index 3d76af9..26682f0 100644 --- a/main.ts +++ b/main.ts @@ -29,10 +29,10 @@ function convertEnumToObject(enumDeclaration: EnumDeclaration): void { const objectDeclaration = `const ${enumName} = {\n ${objectMembers}\n} as const;`; // Create the type definition - const typeDefinition = `type ${enumName} = typeof ${enumName}[keyof typeof ${enumName}];`; + const typeDefinition = `type ${enumName}Type = typeof ${enumName}[keyof typeof ${enumName}];`; // Replace the enum with object and type - enumDeclaration.replaceWithText(`${objectDeclaration}\n\n${typeDefinition}`); + enumDeclaration.replaceWithText(`${objectDeclaration}\n${typeDefinition}`); } function updateEnumReferences(sourceFile: Node, enumNames: string[]): void { @@ -50,31 +50,38 @@ function updateEnumReferences(sourceFile: Node, enumNames: string[]): void { // Check if this is a reference to a converted enum if (enumNames.includes(name)) { // This is a reference to an enum, update it to use the new type - typeRef.replaceWithText(name); + typeRef.replaceWithText(`${name}Type`); } } }); -} - -function updateEnumValueReferences( - sourceFile: Node, - enumNames: string[] -): void { - // Find all property access expressions that might be enum value references - const propertyAccesses = sourceFile.getDescendantsOfKind( - SyntaxKind.PropertyAccessExpression - ); - - propertyAccesses.forEach((propAccess) => { - const expression = propAccess.getExpression(); - if (expression.getKind() === SyntaxKind.Identifier) { - const identifier = expression as Identifier; - const name = identifier.getText(); - // Check if this is a reference to a converted enum - if (enumNames.includes(name)) { - // This is a reference to an enum value, update it to use the new object - propAccess.replaceWithText(propAccess.getText()); + // Find all union types that might contain enum value references + // Update all type nodes that reference enum values (not just unions) + const typeNodes = sourceFile.getDescendantsOfKind(SyntaxKind.TypeReference); + + typeNodes.forEach((typeNode) => { + // We want to find type references of the form EnumName.Property + // In ts-morph, these are QualifiedName nodes inside TypeReferenceNode + const typeName = typeNode.getTypeName(); + if (typeName.getKind() === SyntaxKind.QualifiedName) { + // deno-lint-ignore no-explicit-any + const qualifiedName = typeName as any; + const left = qualifiedName.getLeft(); + const right = qualifiedName.getRight(); + if ( + left.getKind() === SyntaxKind.Identifier && + right.getKind() === SyntaxKind.Identifier + ) { + const enumName = left.getText(); + const propertyName = right.getText(); + // Check if this is a reference to a converted enum value + console.log({ enumName, propertyName }); + if (enumNames.includes(enumName)) { + // Replace the type node with Extract + typeNode.replaceWithText( + `Extract<${enumName}Type, typeof ${enumName}.${propertyName}>` + ); + } } } }); @@ -83,18 +90,18 @@ function updateEnumValueReferences( function runCodemod(filePath: string): void { const sourceFile = project.addSourceFileAtPath(filePath); - // Get all enums before conversion + // Get all enums before conversion and store their names const enums = sourceFile.getEnums(); - const enumNames = enums - .map((enumDecl) => enumDecl.getName()) - .filter(Boolean) as string[]; + const enumNames = enums.map((enumDecl) => enumDecl.getName()).filter(Boolean); + + console.log(`Found enums: ${enumNames.join(", ")}`); // Convert all enums to objects enums.forEach(convertEnumToObject); // Update references after conversion updateEnumReferences(sourceFile, enumNames); - updateEnumValueReferences(sourceFile, enumNames); + // updateEnumValueReferences(sourceFile, enumNames); // Save the changes sourceFile.saveSync(); @@ -102,3 +109,14 @@ function runCodemod(filePath: string): void { // Export the main function export { runCodemod }; + +// Main execution +if (import.meta.main) { + const filePath = Deno.args[0]; + if (!filePath) { + console.error("Please provide a file path as an argument"); + Deno.exit(1); + } + runCodemod(filePath); + console.log(`Successfully converted enums in ${filePath}`); +} diff --git a/sample.ts b/sample.ts index 0a73163..4e94860 100644 --- a/sample.ts +++ b/sample.ts @@ -1,34 +1,38 @@ -enum Color { - Red = "red", - Green = "green", - Blue = "blue", -} - -enum Status { - Pending = 0, - Active = 1, - Inactive = 2, -} - -enum Direction { - North = "NORTH", - South = "SOUTH", - East = "EAST", - West = "WEST", -} - -enum Priority { - Low = 0, - Medium = 1, - High = 2, - Critical = "CRITICAL", -} - -function _getColorName(color: Color): string { +const Color = { + Red: "red", + Green: "green", + Blue: "blue" +} as const; +type ColorType = typeof Color[keyof typeof Color]; + +const Status = { + Pending: 0, + Active: 1, + Inactive: 2 +} as const; +type StatusType = typeof Status[keyof typeof Status]; + +const Direction = { + North: "NORTH", + South: "SOUTH", + East: "EAST", + West: "WEST" +} as const; +type DirectionType = typeof Direction[keyof typeof Direction]; + +const Priority = { + Low: 0, + Medium: 1, + High: 2, + Critical: "CRITICAL" +} as const; +type PriorityType = typeof Priority[keyof typeof Priority]; + +function _getColorName(color: ColorType): string { return color; } -function _getStatusText(status: Status): string { +function _getStatusText(status: StatusType): string { switch (status) { case Status.Pending: return "Pending"; @@ -44,12 +48,12 @@ function _getStatusText(status: Status): string { interface User { id: number; name: string; - status: Status; - favoriteColor: Color; + status: StatusType; + favoriteColor: ColorType; } class Navigation { - private currentDirection: Direction = Direction.North; + private currentDirection: DirectionType = Direction.North; turnLeft(): void { switch (this.currentDirection) { @@ -68,7 +72,7 @@ class Navigation { } } - getDirection(): Direction { + getDirection(): DirectionType { return this.currentDirection; } } @@ -79,9 +83,9 @@ function processTask(priority: TaskPriority): void { console.log(`Processing task with priority: ${priority}`); } -const _allColors: Color[] = [Color.Red, Color.Green, Color.Blue]; +const _allColors: ColorType[] = [Color.Red, Color.Green, Color.Blue]; -const _colorMap: Record = { +const _colorMap: Record = { [Color.Red]: "#FF0000", [Color.Green]: "#00FF00", [Color.Blue]: "#0000FF", diff --git a/sample_backup.ts b/sample_backup.ts index 0a73163..4e94860 100644 --- a/sample_backup.ts +++ b/sample_backup.ts @@ -1,34 +1,38 @@ -enum Color { - Red = "red", - Green = "green", - Blue = "blue", -} - -enum Status { - Pending = 0, - Active = 1, - Inactive = 2, -} - -enum Direction { - North = "NORTH", - South = "SOUTH", - East = "EAST", - West = "WEST", -} - -enum Priority { - Low = 0, - Medium = 1, - High = 2, - Critical = "CRITICAL", -} - -function _getColorName(color: Color): string { +const Color = { + Red: "red", + Green: "green", + Blue: "blue" +} as const; +type ColorType = typeof Color[keyof typeof Color]; + +const Status = { + Pending: 0, + Active: 1, + Inactive: 2 +} as const; +type StatusType = typeof Status[keyof typeof Status]; + +const Direction = { + North: "NORTH", + South: "SOUTH", + East: "EAST", + West: "WEST" +} as const; +type DirectionType = typeof Direction[keyof typeof Direction]; + +const Priority = { + Low: 0, + Medium: 1, + High: 2, + Critical: "CRITICAL" +} as const; +type PriorityType = typeof Priority[keyof typeof Priority]; + +function _getColorName(color: ColorType): string { return color; } -function _getStatusText(status: Status): string { +function _getStatusText(status: StatusType): string { switch (status) { case Status.Pending: return "Pending"; @@ -44,12 +48,12 @@ function _getStatusText(status: Status): string { interface User { id: number; name: string; - status: Status; - favoriteColor: Color; + status: StatusType; + favoriteColor: ColorType; } class Navigation { - private currentDirection: Direction = Direction.North; + private currentDirection: DirectionType = Direction.North; turnLeft(): void { switch (this.currentDirection) { @@ -68,7 +72,7 @@ class Navigation { } } - getDirection(): Direction { + getDirection(): DirectionType { return this.currentDirection; } } @@ -79,9 +83,9 @@ function processTask(priority: TaskPriority): void { console.log(`Processing task with priority: ${priority}`); } -const _allColors: Color[] = [Color.Red, Color.Green, Color.Blue]; +const _allColors: ColorType[] = [Color.Red, Color.Green, Color.Blue]; -const _colorMap: Record = { +const _colorMap: Record = { [Color.Red]: "#FF0000", [Color.Green]: "#00FF00", [Color.Blue]: "#0000FF", diff --git a/sample_converted.ts b/sample_converted.ts index a8b8f13..4e94860 100644 --- a/sample_converted.ts +++ b/sample_converted.ts @@ -3,16 +3,14 @@ const Color = { Green: "green", Blue: "blue" } as const; - -type Color = typeof Color[keyof typeof Color]; +type ColorType = typeof Color[keyof typeof Color]; const Status = { Pending: 0, Active: 1, Inactive: 2 } as const; - -type Status = typeof Status[keyof typeof Status]; +type StatusType = typeof Status[keyof typeof Status]; const Direction = { North: "NORTH", @@ -20,8 +18,7 @@ const Direction = { East: "EAST", West: "WEST" } as const; - -type Direction = typeof Direction[keyof typeof Direction]; +type DirectionType = typeof Direction[keyof typeof Direction]; const Priority = { Low: 0, @@ -29,14 +26,13 @@ const Priority = { High: 2, Critical: "CRITICAL" } as const; +type PriorityType = typeof Priority[keyof typeof Priority]; -type Priority = typeof Priority[keyof typeof Priority]; - -function _getColorName(color: Color): string { +function _getColorName(color: ColorType): string { return color; } -function _getStatusText(status: Status): string { +function _getStatusText(status: StatusType): string { switch (status) { case Status.Pending: return "Pending"; @@ -52,12 +48,12 @@ function _getStatusText(status: Status): string { interface User { id: number; name: string; - status: Status; - favoriteColor: Color; + status: StatusType; + favoriteColor: ColorType; } class Navigation { - private currentDirection: Direction = Direction.North; + private currentDirection: DirectionType = Direction.North; turnLeft(): void { switch (this.currentDirection) { @@ -76,7 +72,7 @@ class Navigation { } } - getDirection(): Direction { + getDirection(): DirectionType { return this.currentDirection; } } @@ -87,9 +83,9 @@ function processTask(priority: TaskPriority): void { console.log(`Processing task with priority: ${priority}`); } -const _allColors: Color[] = [Color.Red, Color.Green, Color.Blue]; +const _allColors: ColorType[] = [Color.Red, Color.Green, Color.Blue]; -const _colorMap: Record = { +const _colorMap: Record = { [Color.Red]: "#FF0000", [Color.Green]: "#00FF00", [Color.Blue]: "#0000FF",