diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index a1df5d2..a3cd867 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -28,6 +28,7 @@ - **Complex Types**: Union and intersection types, nested object structures, template literal types - **Utility Types**: Built-in support for Pick, Omit, Partial, Required, Record, Readonly, and other TypeScript utility types - **Advanced Features**: Conditional types, mapped types, keyof operators, indexed access types +- **JavaScript Built-in Types**: Native support for Date type using TypeBox's JavaScript type system - **Import Resolution**: Cross-file type dependencies with qualified naming and circular dependency handling ## Core Components @@ -133,8 +134,9 @@ The handler system in , 7. **Advanced Handlers**: , , 8. **Function Handlers**: -9. **Type Query Handlers**: , -10. **Access Handlers**: , +9. **JavaScript Type Handlers**: - Handles JavaScript built-in types like Date using TypeBox's extended type system +10. **Type Query Handlers**: , +11. **Access Handlers**: , #### Readonly Type Handling @@ -157,11 +159,20 @@ This dual approach ensures proper handling of both TypeScript readonly construct - `type ReadonlyArray = readonly string[]` (array modifier) - `type ReadonlyTuple = readonly [string, number]` (tuple modifier) +#### JavaScript Built-in Type Support + +The system provides comprehensive support for JavaScript built-in types through specialized handlers: + +- **Date Type Handler**: The handles TypeScript's `Date` type references and converts them to TypeBox's `Type.Date()` schema +- **Type Reference Registration**: JavaScript built-in types are registered in the `typeReferenceHandlers` map for O(1) lookup performance +- **Extended Type System**: Leverages TypeBox's JavaScript type system for types that extend beyond standard JSON Schema + #### Handler Management The class orchestrates all handlers through: - **Handler Caching**: Caches handler instances for performance optimization +- **Type Reference Mapping**: O(1) lookup for built-in types like Date, utility types like Partial, and other type references - **Fallback System**: Provides fallback handlers for complex cases including readonly array modifiers ### Import Resolution diff --git a/src/handlers/typebox/date-type-handler.ts b/src/handlers/typebox/date-type-handler.ts new file mode 100644 index 0000000..bd95dcb --- /dev/null +++ b/src/handlers/typebox/date-type-handler.ts @@ -0,0 +1,16 @@ +import { BaseTypeHandler } from '@daxserver/validation-schema-codegen/handlers/typebox/base-type-handler' +import { makeTypeCall } from '@daxserver/validation-schema-codegen/utils/typebox-codegen-utils' +import { Node, ts, TypeReferenceNode } from 'ts-morph' + +export class DateTypeHandler extends BaseTypeHandler { + canHandle(node: TypeReferenceNode): boolean { + const typeName = node.getTypeName() + + return Node.isIdentifier(typeName) && typeName.getText() === 'Date' + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + handle(_node: TypeReferenceNode): ts.Expression { + return makeTypeCall('Date') + } +} diff --git a/src/handlers/typebox/typebox-type-handlers.ts b/src/handlers/typebox/typebox-type-handlers.ts index c96f603..b1c359c 100644 --- a/src/handlers/typebox/typebox-type-handlers.ts +++ b/src/handlers/typebox/typebox-type-handlers.ts @@ -3,6 +3,7 @@ import { ArrayTypeHandler } from '@daxserver/validation-schema-codegen/handlers/ import { IntersectionTypeHandler } from '@daxserver/validation-schema-codegen/handlers/typebox/collection/intersection-type-handler' import { TupleTypeHandler } from '@daxserver/validation-schema-codegen/handlers/typebox/collection/tuple-type-handler' import { UnionTypeHandler } from '@daxserver/validation-schema-codegen/handlers/typebox/collection/union-type-handler' +import { DateTypeHandler } from '@daxserver/validation-schema-codegen/handlers/typebox/date-type-handler' import { FunctionTypeHandler } from '@daxserver/validation-schema-codegen/handlers/typebox/function-type-handler' import { IndexedAccessTypeHandler } from '@daxserver/validation-schema-codegen/handlers/typebox/indexed-access-type-handler' import { KeyOfTypeHandler } from '@daxserver/validation-schema-codegen/handlers/typebox/keyof-type-handler' @@ -52,6 +53,7 @@ export class TypeBoxTypeHandlers { const typeofTypeHandler = new TypeofTypeHandler() const readonlyTypeHandler = new ReadonlyTypeHandler() const readonlyArrayTypeHandler = new ReadonlyArrayTypeHandler() + const dateTypeHandler = new DateTypeHandler() // O(1) lookup by SyntaxKind this.syntaxKindHandlers.set(SyntaxKind.AnyKeyword, simpleTypeHandler) @@ -83,6 +85,7 @@ export class TypeBoxTypeHandlers { this.typeReferenceHandlers.set('Omit', omitTypeHandler) this.typeReferenceHandlers.set('Required', requiredTypeHandler) this.typeReferenceHandlers.set('Readonly', readonlyTypeHandler) + this.typeReferenceHandlers.set('Date', dateTypeHandler) // Fallback handlers for complex cases this.fallbackHandlers = [ diff --git a/tests/handlers/typebox/date-types.test.ts b/tests/handlers/typebox/date-types.test.ts new file mode 100644 index 0000000..2f94961 --- /dev/null +++ b/tests/handlers/typebox/date-types.test.ts @@ -0,0 +1,113 @@ +import { createSourceFile, formatWithPrettier, generateFormattedCode } from '@test-fixtures/utils' +import { beforeEach, describe, expect, test } from 'bun:test' +import { Project } from 'ts-morph' + +describe('Date types', () => { + let project: Project + + beforeEach(() => { + project = new Project() + }) + + test('without export', () => { + const sourceFile = createSourceFile(project, `type A = Date`) + + expect(generateFormattedCode(sourceFile)).toBe( + formatWithPrettier(` + export const A = Type.Date(); + + export type A = Static; + `), + ) + }) + + test('with export', () => { + const sourceFile = createSourceFile(project, `export type A = Date`) + + expect(generateFormattedCode(sourceFile)).toBe( + formatWithPrettier(` + export const A = Type.Date(); + + export type A = Static; + `), + ) + }) + + test('simple Date type alias', () => { + const sourceFile = createSourceFile(project, `type Timestamp = Date`) + + expect(generateFormattedCode(sourceFile)).toBe( + formatWithPrettier(` + export const Timestamp = Type.Date(); + + export type Timestamp = Static; + `), + ) + }) + + test('Date in object property', () => { + const sourceFile = createSourceFile( + project, + ` + interface User { + name: string; + createdAt: Date; + } + `, + ) + + expect(generateFormattedCode(sourceFile)).toBe( + formatWithPrettier(` + export const User = Type.Object({ + name: Type.String(), + createdAt: Type.Date(), + }); + + export type User = Static; + `), + ) + }) + + test('Date in union type', () => { + const sourceFile = createSourceFile(project, `type Value = string | Date | number`) + + expect(generateFormattedCode(sourceFile)).toBe( + formatWithPrettier(` + export const Value = Type.Union([Type.String(), Type.Date(), Type.Number()]); + + export type Value = Static; + `), + ) + }) + + test('Date in array', () => { + const sourceFile = createSourceFile(project, `type Dates = Date[]`) + + expect(generateFormattedCode(sourceFile)).toBe( + formatWithPrettier(` + export const Dates = Type.Array(Type.Date()); + + export type Dates = Static; + `), + ) + }) + + test('Date in function parameter and return type', () => { + const sourceFile = createSourceFile( + project, + ` + function formatDate(date: Date): string { + return date.toString(); + } + `, + ) + + expect(generateFormattedCode(sourceFile)).toBe( + formatWithPrettier(` + export const formatDate = Type.Function([Type.Date()], Type.String()); + + export type formatDate = Static; + `), + ) + }) +})