Skip to content

Commit 58a5261

Browse files
authored
refactor: re-organize handlers (#6)
1 parent 90e2306 commit 58a5261

40 files changed

+510
-511
lines changed

ARCHITECTURE.md

Lines changed: 41 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -166,33 +166,57 @@ const result = await generateCode({
166166
- <mcfile name="typebox-call.ts" path="src/utils/typebox-call.ts"></mcfile>: Contains the core logic for converting TypeScript type nodes into TypeBox `Type` expressions. `getTypeBoxType` takes a `TypeNode` as input and returns a `ts.Node` representing the equivalent TypeBox schema.
167167
- <mcfile name="add-static-type-alias.ts" path="src/utils/add-static-type-alias.ts"></mcfile>: Generates and adds the `export type [TypeName] = Static<typeof [TypeName]>` declaration to the output source file. This declaration is essential for enabling TypeScript's static type inference from the dynamically generated TypeBox schemas, ensuring type safety at compile time.
168168
- <mcfile name="typebox-codegen-utils.ts" path="src/utils/typebox-codegen-utils.ts"></mcfile>: Contains general utility functions that support the TypeBox code generation process, such as helper functions for string manipulation or AST node creation.
169-
- <mcfile name="typescript-ast-parser.ts" path="src/utils/typescript-ast-parser.ts"></mcfile>: Responsible for parsing TypeScript source code and extracting relevant Abstract Syntax Tree (AST) information. It provides functions to navigate the AST and identify specific nodes like type aliases, interfaces, or enums.
170-
- <mcfile name="typescript-ast-types.ts" path="src/utils/typescript-ast-types.ts"></mcfile>: Defines custom types and interfaces that represent the structured AST information extracted by `typescript-ast-parser.ts`, providing a consistent data model for further processing.
171169

172170
### Handlers Directory
173171

174-
This directory contains a collection of specialized handler modules, each responsible for converting a specific type of TypeScript AST node into its corresponding TypeBox schema. This modular approach allows for easy extension and maintenance of the type mapping logic.
172+
This directory contains a collection of specialized handler modules, each responsible for converting a specific type of TypeScript AST node into its corresponding TypeBox schema. The handlers follow a hierarchical architecture with specialized base classes to reduce code duplication and ensure consistent behavior.
173+
174+
#### Base Handler Classes
175+
176+
- <mcfile name="base-type-handler.ts" path="src/handlers/typebox/base-type-handler.ts"></mcfile>: The root abstract base class that defines the common interface for all type handlers. Provides the `canHandle` and `handle` methods, along with utility functions like `makeTypeCall` for creating TypeBox expressions.
177+
- <mcfile name="type-reference-base-handler.ts" path="src/handlers/typebox/reference/type-reference-base-handler.ts"></mcfile>: Specialized base class for utility type handlers that work with TypeScript type references. Provides `validateTypeReference` and `extractTypeArguments` methods for consistent handling of generic utility types like `Partial<T>`, `Pick<T, K>`, etc.
178+
- <mcfile name="object-like-base-handler.ts" path="src/handlers/typebox/object/object-like-base-handler.ts"></mcfile>: Base class for handlers that process object-like structures (objects and interfaces). Provides `processProperties`, `extractProperties`, and `createObjectType` methods for consistent property handling and TypeBox object creation.
179+
- <mcfile name="collection-base-handler.ts" path="src/handlers/typebox/collection/collection-base-handler.ts"></mcfile>: Base class for handlers that work with collections of types (arrays, tuples, unions, intersections). Provides `processTypeCollection`, `processSingleType`, and `validateNonEmptyCollection` methods for consistent type collection processing.
180+
181+
#### Type Handler Implementations
182+
183+
**Utility Type Handlers** (extend `TypeReferenceBaseHandler`):
184+
185+
- <mcfile name="partial-type-handler.ts" path="src/handlers/typebox/reference/partial-type-handler.ts"></mcfile>: Handles TypeScript `Partial` utility types.
186+
- <mcfile name="pick-type-handler.ts" path="src/handlers/typebox/reference/pick-type-handler.ts"></mcfile>: Handles TypeScript `Pick` utility types.
187+
- <mcfile name="omit-type-handler.ts" path="src/handlers/typebox/reference/omit-type-handler.ts"></mcfile>: Handles TypeScript `Omit` utility types.
188+
- <mcfile name="required-type-handler.ts" path="src/handlers/typebox/reference/required-type-handler.ts"></mcfile>: Handles TypeScript `Required` utility types.
189+
- <mcfile name="record-type-handler.ts" path="src/handlers/typebox/reference/record-type-handler.ts"></mcfile>: Handles TypeScript `Record` utility types.
190+
191+
**Object-Like Type Handlers** (extend `ObjectLikeBaseHandler`):
192+
193+
- <mcfile name="object-type-handler.ts" path="src/handlers/typebox/object/object-type-handler.ts"></mcfile>: Handles TypeScript object types and type literals.
194+
- <mcfile name="interface-type-handler.ts" path="src/handlers/typebox/object/interface-type-handler.ts"></mcfile>: Handles TypeScript interface declarations.
195+
196+
**Collection Type Handlers** (extend `CollectionBaseHandler`):
197+
198+
- <mcfile name="array-type-handler.ts" path="src/handlers/typebox/collection/array-type-handler.ts"></mcfile>: Handles TypeScript array types (e.g., `string[]`, `Array<number>`).
199+
- <mcfile name="tuple-type-handler.ts" path="src/handlers/typebox/collection/tuple-type-handler.ts"></mcfile>: Handles TypeScript tuple types.
200+
- <mcfile name="union-type-handler.ts" path="src/handlers/typebox/collection/union-type-handler.ts"></mcfile>: Handles TypeScript union types (e.g., `string | number`).
201+
- <mcfile name="intersection-type-handler.ts" path="src/handlers/typebox/collection/intersection-type-handler.ts"></mcfile>: Handles TypeScript intersection types (e.g., `TypeA & TypeB`).
202+
203+
**Standalone Type Handlers** (extend `BaseTypeHandler`):
175204

176-
- <mcfile name="array-type-handler.ts" path="src/handlers/typebox/array-type-handler.ts"></mcfile>: Handles TypeScript array types (e.g., `string[]`, `Array<number>`).
177-
- <mcfile name="indexed-access-type-handler.ts" path="src/handlers/typebox/indexed-access-type-handler.ts"></mcfile>: Handles TypeScript indexed access types (e.g., `Type[Key]`).
178-
- <mcfile name="intersection-type-handler.ts" path="src/handlers/typebox/intersection-type-handler.ts"></mcfile>: Handles TypeScript intersection types (e.g., `TypeA & TypeB`).
179-
- <mcfile name="literal-type-handler.ts" path="src/handlers/typebox/literal-type-handler.ts"></mcfile>: Handles TypeScript literal types (e.g., `'hello'`, `123`, `true`).
180-
- <mcfile name="object-type-handler.ts" path="src/handlers/typebox/object-type-handler.ts"></mcfile>: Handles TypeScript object types and interfaces.
181-
- <mcfile name="omit-type-handler.ts" path="src/handlers/typebox/omit-type-handler.ts"></mcfile>: Handles TypeScript `Omit` utility types.
182-
- <mcfile name="partial-type-handler.ts" path="src/handlers/typebox/partial-type-handler.ts"></mcfile>: Handles TypeScript `Partial` utility types.
183-
- <mcfile name="pick-type-handler.ts" path="src/handlers/typebox/pick-type-handler.ts"></mcfile>: Handles TypeScript `Pick` utility types.
184-
- <mcfile name="required-type-handler.ts" path="src/handlers/typebox/required-type-handler.ts"></mcfile>: Handles TypeScript `Required` utility types.
185-
- <mcfile name="record-type-handler.ts" path="src/handlers/typebox/record-type-handler.ts"></mcfile>: Handles TypeScript `Record` utility types.
186205
- <mcfile name="simple-type-handler.ts" path="src/handlers/typebox/simple-type-handler.ts"></mcfile>: Handles basic TypeScript types like `string`, `number`, `boolean`, `null`, `undefined`, `any`, `unknown`, `void`.
206+
- <mcfile name="literal-type-handler.ts" path="src/handlers/typebox/literal-type-handler.ts"></mcfile>: Handles TypeScript literal types (e.g., `'hello'`, `123`, `true`).
187207
- <mcfile name="function-type-handler.ts" path="src/handlers/typebox/function-type-handler.ts"></mcfile>: Handles TypeScript function types and function declarations, including parameter types, optional parameters, and return types.
188208
- <mcfile name="template-literal-type-handler.ts" path="src/handlers/typebox/template-literal-type-handler.ts"></mcfile>: Handles TypeScript template literal types (e.g., `` `hello-${string}` ``). Parses template literals into components, handling literal text, embedded types (string, number, unions), and string/numeric literals.
189209
- <mcfile name="typeof-type-handler.ts" path="src/handlers/typebox/typeof-type-handler.ts"></mcfile>: Handles TypeScript `typeof` expressions for extracting types from values.
190-
- <mcfile name="tuple-type-handler.ts" path="src/handlers/typebox/tuple-type-handler.ts"></mcfile>: Handles TypeScript tuple types.
191-
- <mcfile name="type-operator-handler.ts" path="src/handlers/typebox/type-operator-handler.ts"></mcfile>: Handles TypeScript type operators like `keyof`, `typeof`.
210+
- <mcfile name="keyof-type-handler.ts" path="src/handlers/typebox/keyof-type-handler.ts"></mcfile>: Handles TypeScript `keyof` type operator for extracting object keys.
211+
- <mcfile name="readonly-type-handler.ts" path="src/handlers/typebox/readonly-type-handler.ts"></mcfile>: Handles TypeScript `readonly` type modifier for creating immutable types.
212+
- <mcfile name="type-operator-handler.ts" path="src/handlers/typebox/type-operator-handler.ts"></mcfile>: Fallback handler for other TypeScript type operators not covered by specific handlers.
192213
- <mcfile name="type-reference-handler.ts" path="src/handlers/typebox/type-reference-handler.ts"></mcfile>: Handles references to other types (e.g., `MyType`).
214+
- <mcfile name="indexed-access-type-handler.ts" path="src/handlers/typebox/indexed-access-type-handler.ts"></mcfile>: Handles TypeScript indexed access types (e.g., `Type[Key]`).
193215
- <mcfile name="typebox-type-handler.ts" path="src/handlers/typebox/typebox-type-handler.ts"></mcfile>: A generic handler for TypeBox types.
194-
- <mcfile name="typebox-type-handlers.ts" path="src/handlers/typebox/typebox-type-handlers.ts"></mcfile>: Orchestrates the use of the individual type handlers, acting as a dispatcher based on the type of AST node encountered.
195-
- <mcfile name="union-type-handler.ts" path="src/handlers/typebox/union-type-handler.ts"></mcfile>: Handles TypeScript union types (e.g., `string | number`).
216+
217+
**Handler Orchestration**:
218+
219+
- <mcfile name="typebox-type-handlers.ts" path="src/handlers/typebox/typebox-type-handlers.ts"></mcfile>: Orchestrates the use of the individual type handlers, acting as a dispatcher based on the type of AST node encountered. Uses optimized lookup mechanisms for performance with O(1) syntax kind-based lookups and type reference name mappings. Includes specialized handlers for type operators (KeyOfTypeHandler, TypeofTypeHandler, ReadonlyTypeHandler) and maintains fallback handlers for edge cases requiring custom logic.
196220

197221
### Parsers Directory
198222

src/handlers/typebox/array-type-handler.ts

Lines changed: 0 additions & 22 deletions
This file was deleted.
Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,6 @@
11
import { Node, ts } from 'ts-morph'
22

33
export abstract class BaseTypeHandler {
4-
protected getTypeBoxType: (typeNode?: Node) => ts.Expression
5-
6-
constructor(getTypeBoxType: (typeNode?: Node) => ts.Expression) {
7-
this.getTypeBoxType = getTypeBoxType
8-
}
9-
10-
abstract canHandle(typeNode: Node | undefined): boolean
11-
abstract handle(typeNode: Node | undefined): ts.Expression
4+
abstract canHandle(node: Node): boolean
5+
abstract handle(node: Node): ts.Expression
126
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { CollectionBaseHandler } from '@daxserver/validation-schema-codegen/handlers/typebox/collection/collection-base-handler'
2+
import { ArrayTypeNode, Node, ts } from 'ts-morph'
3+
4+
export class ArrayTypeHandler extends CollectionBaseHandler {
5+
canHandle(node: Node): boolean {
6+
return Node.isArrayTypeNode(node)
7+
}
8+
9+
handle(node: ArrayTypeNode): ts.Expression {
10+
return this.processSingleType(node.getElementTypeNode(), 'Array')
11+
}
12+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { BaseTypeHandler } from '@daxserver/validation-schema-codegen/handlers/typebox/base-type-handler'
2+
import { getTypeBoxType } from '@daxserver/validation-schema-codegen/utils/typebox-call'
3+
import { makeTypeCall } from '@daxserver/validation-schema-codegen/utils/typebox-codegen-utils'
4+
import { Node, ts } from 'ts-morph'
5+
6+
export abstract class CollectionBaseHandler extends BaseTypeHandler {
7+
protected processTypeCollection(nodes: Node[], typeBoxFunction: string): ts.Expression {
8+
const typeBoxTypes = nodes.map((node) => getTypeBoxType(node))
9+
const arrayLiteral = ts.factory.createArrayLiteralExpression(typeBoxTypes)
10+
11+
return makeTypeCall(typeBoxFunction, [arrayLiteral])
12+
}
13+
14+
protected processSingleType(node: Node, typeBoxFunction: string): ts.Expression {
15+
return makeTypeCall(typeBoxFunction, [getTypeBoxType(node)])
16+
}
17+
18+
protected validateNonEmptyCollection(nodes: Node[], typeName: string): void {
19+
if (nodes.length === 0) {
20+
throw new Error(`${typeName} must have at least one type`)
21+
}
22+
}
23+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { CollectionBaseHandler } from '@daxserver/validation-schema-codegen/handlers/typebox/collection/collection-base-handler'
2+
import { IntersectionTypeNode, Node, ts } from 'ts-morph'
3+
4+
export class IntersectionTypeHandler extends CollectionBaseHandler {
5+
canHandle(node: Node): boolean {
6+
return Node.isIntersectionTypeNode(node)
7+
}
8+
9+
handle(node: IntersectionTypeNode): ts.Expression {
10+
return this.processTypeCollection(node.getTypeNodes(), 'Intersect')
11+
}
12+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { CollectionBaseHandler } from '@daxserver/validation-schema-codegen/handlers/typebox/collection/collection-base-handler'
2+
import { Node, ts, TupleTypeNode } from 'ts-morph'
3+
4+
export class TupleTypeHandler extends CollectionBaseHandler {
5+
canHandle(node: Node): boolean {
6+
return Node.isTupleTypeNode(node)
7+
}
8+
9+
handle(node: TupleTypeNode): ts.Expression {
10+
return this.processTypeCollection(node.getElements(), 'Tuple')
11+
}
12+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { CollectionBaseHandler } from '@daxserver/validation-schema-codegen/handlers/typebox/collection/collection-base-handler'
2+
import { Node, ts, UnionTypeNode } from 'ts-morph'
3+
4+
export class UnionTypeHandler extends CollectionBaseHandler {
5+
canHandle(node: Node): boolean {
6+
return Node.isUnionTypeNode(node)
7+
}
8+
9+
handle(node: UnionTypeNode): ts.Expression {
10+
return this.processTypeCollection(node.getTypeNodes(), 'Union')
11+
}
12+
}

src/handlers/typebox/function-type-handler.ts

Lines changed: 9 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,21 @@
11
import { BaseTypeHandler } from '@daxserver/validation-schema-codegen/handlers/typebox/base-type-handler'
2+
import { getTypeBoxType } from '@daxserver/validation-schema-codegen/utils/typebox-call'
23
import { makeTypeCall } from '@daxserver/validation-schema-codegen/utils/typebox-codegen-utils'
3-
import { Node, ts } from 'ts-morph'
4+
import { FunctionTypeNode, Node, ts } from 'ts-morph'
45

56
export class FunctionTypeHandler extends BaseTypeHandler {
6-
constructor(getTypeBoxType: (typeNode?: Node) => ts.Expression) {
7-
super(getTypeBoxType)
7+
canHandle(node: Node): boolean {
8+
return Node.isFunctionTypeNode(node)
89
}
910

10-
canHandle(typeNode?: Node): boolean {
11-
return Node.isFunctionTypeNode(typeNode)
12-
}
13-
14-
handle(typeNode: Node): ts.Expression {
15-
if (!Node.isFunctionTypeNode(typeNode)) {
16-
return makeTypeCall('Any')
17-
}
18-
19-
const parameters = typeNode.getParameters()
20-
const returnType = typeNode.getReturnTypeNode()
11+
handle(node: FunctionTypeNode): ts.Expression {
12+
const parameters = node.getParameters()
13+
const returnType = node.getReturnTypeNode()
2114

2215
// Convert parameters to TypeBox types
2316
const parameterTypes = parameters.map((param) => {
2417
const paramTypeNode = param.getTypeNode()
25-
const paramType = this.getTypeBoxType(paramTypeNode)
18+
const paramType = getTypeBoxType(paramTypeNode)
2619

2720
// Check if parameter is optional
2821
if (param.hasQuestionToken()) {
@@ -33,7 +26,7 @@ export class FunctionTypeHandler extends BaseTypeHandler {
3326
})
3427

3528
// Convert return type to TypeBox type
36-
const returnTypeBox = this.getTypeBoxType(returnType)
29+
const returnTypeBox = getTypeBoxType(returnType)
3730

3831
// Create TypeBox Function call with parameters array and return type
3932
return makeTypeCall('Function', [

src/handlers/typebox/indexed-access-type-handler.ts

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,27 @@
11
import { BaseTypeHandler } from '@daxserver/validation-schema-codegen/handlers/typebox/base-type-handler'
2+
import { getTypeBoxType } from '@daxserver/validation-schema-codegen/utils/typebox-call'
23
import { makeTypeCall } from '@daxserver/validation-schema-codegen/utils/typebox-codegen-utils'
34
import { IndexedAccessTypeNode, Node, ts } from 'ts-morph'
45

56
export class IndexedAccessTypeHandler extends BaseTypeHandler {
6-
canHandle(node: Node | undefined): boolean {
7-
return node !== undefined && node.isKind(ts.SyntaxKind.IndexedAccessType)
7+
canHandle(node: Node): boolean {
8+
return node.isKind(ts.SyntaxKind.IndexedAccessType)
89
}
910

10-
handle(node: Node): ts.Expression {
11-
const typeNode = node as IndexedAccessTypeNode
12-
const objectType = typeNode.getObjectTypeNode()
13-
const indexType = typeNode.getIndexTypeNode()
11+
handle(node: IndexedAccessTypeNode): ts.Expression {
12+
const objectType = node.getObjectTypeNode()
13+
const indexType = node.getIndexTypeNode()
1414

1515
// Handle special case: typeof A[number] where A is a readonly tuple
1616
if (
1717
objectType?.isKind(ts.SyntaxKind.TypeQuery) &&
1818
indexType?.isKind(ts.SyntaxKind.NumberKeyword)
1919
) {
20-
return this.handleTypeofArrayAccess(objectType, typeNode)
20+
return this.handleTypeofArrayAccess(objectType, node)
2121
}
2222

23-
const typeboxObjectType = this.getTypeBoxType(objectType)
24-
const typeboxIndexType = this.getTypeBoxType(indexType)
23+
const typeboxObjectType = getTypeBoxType(objectType)
24+
const typeboxIndexType = getTypeBoxType(indexType)
2525

2626
return makeTypeCall('Index', [typeboxObjectType, typeboxIndexType])
2727
}
@@ -58,8 +58,8 @@ export class IndexedAccessTypeHandler extends BaseTypeHandler {
5858
}
5959

6060
// Fallback to default Index behavior
61-
const typeboxObjectType = this.getTypeBoxType(typeQuery)
62-
const typeboxIndexType = this.getTypeBoxType(indexedAccessType.getIndexTypeNode())
61+
const typeboxObjectType = getTypeBoxType(typeQuery)
62+
const typeboxIndexType = getTypeBoxType(indexedAccessType.getIndexTypeNode())
6363
return makeTypeCall('Index', [typeboxObjectType, typeboxIndexType])
6464
}
6565

0 commit comments

Comments
 (0)