diff --git a/.vscode/settings.json b/.vscode/settings.json index 679e7f3..4b23af4 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -20,4 +20,9 @@ }, "js/ts.tsdk.path": "node_modules/typescript/lib", + "js/ts.format.enabled": false, + "js/ts.tsdk.promptToUseWorkspaceVersion": true, + "cSpell.words": [ + "SDPE" + ], } \ No newline at end of file diff --git a/eslint.config.js b/eslint.config.js index 49f2e94..9a37b93 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -11,7 +11,7 @@ export default [ }, }, files: ["**/*.test.ts", "**/*.bench.ts", "test/**/*.ts"], - ignores: ["**/*.d.ts"] + ignores: ["**/*.d.ts", "**/*.gen.ts"] }, { ...duplojsEslintOpen, @@ -23,7 +23,7 @@ export default [ }, }, files: ["**/*.ts"], - ignores: ["**/*.test.ts", "**/*.bench.ts", "test/**/*.ts", "**/*.d.ts"], + ignores: ["**/*.test.ts", "**/*.bench.ts", "test/**/*.ts", "**/*.d.ts", "**/*.gen.ts"], }, { ignores: ["coverage", "dist"] diff --git a/integration/toDataParser/__snapshots__/basic.gen.ts b/integration/toDataParser/__snapshots__/basic.gen.ts new file mode 100644 index 0000000..f243c17 --- /dev/null +++ b/integration/toDataParser/__snapshots__/basic.gen.ts @@ -0,0 +1,38 @@ +import * as DP from "@duplojs/utils/dataParser"; + +export const userDataParser = DP.object({ + id: DP.templateLiteral(["user-", DP.number(), "-db1"]), + name: DP.string({ checkers: [DP.checkerStringMin(2)] }), + email: DP.string({ checkers: [DP.checkerEmail()] }), + role: DP.literal(["admin", "editor", "viewer"]), + age: DP.optional(DP.number({ + coerce: true, + checkers: [ + DP.checkerNumberMin(0), + DP.checkerNumberMax(80) + ] + })), + contact: DP.union([ + DP.object({ + phone: DP.string({ checkers: [DP.checkerRegex(/^[+\d][\d\s-]{5,}$/)] }) + }), + DP.object({ + email: DP.string({ checkers: [DP.checkerEmail()] }) + }) + ]), + address: DP.object({ + street: DP.string(), + city: DP.string(), + zip: DP.string({ checkers: [DP.checkerRegex(/^\d{5}$/)] }), + country: DP.literal(["FR"]) + }), + roles: DP.array(DP.literal(["admin", "editor", "viewer"]), { checkers: [DP.checkerArrayMin(1)] }), + preferences: DP.nullable(DP.object({ + theme: DP.literal(["light", "dark"]), + newsletter: DP.boolean() + })), + metadata: DP.record(DP.string(), DP.string()), + location: DP.tuple([DP.number(), DP.number()], { rest: DP.number() }), + createdAt: DP.date({ coerce: true }), + startAt: DP.time() +}); diff --git a/integration/toDataParser/__snapshots__/recursive.gen.ts b/integration/toDataParser/__snapshots__/recursive.gen.ts new file mode 100644 index 0000000..8d072f4 --- /dev/null +++ b/integration/toDataParser/__snapshots__/recursive.gen.ts @@ -0,0 +1,40 @@ +import * as DP from "@duplojs/utils/dataParser"; + +export type RecursiveType0 = { + id: string; + replies: RecursiveType0[]; +}; + +export type $recursive1DataParser = RecursiveType0; + +export type RecursiveType2 = [ + string, + RecursiveType2[] +]; + +export type $recursive2DataParser = RecursiveType2; + +export type RecursiveType4 = { + name: string; + children: RecursiveType4[]; + comment: RecursiveType0; + meta: RecursiveType2; +}; + +export type $recursive0DataParser = RecursiveType4; + +export const recursive1DataParser: DP.DataParser<$recursive1DataParser> = DP.object({ + id: DP.string(), + replies: DP.array(DP.lazy(() => recursive1DataParser)) +}); + +export const recursive2DataParser: DP.DataParser<$recursive2DataParser> = DP.tuple([DP.string(), DP.array(DP.lazy(() => recursive2DataParser))]); + +export const recursive0DataParser: DP.DataParser<$recursive0DataParser> = DP.object({ + name: DP.string(), + children: DP.array(DP.lazy(() => recursive0DataParser)), + comment: DP.lazy(() => recursive1DataParser), + meta: DP.lazy(() => recursive2DataParser) +}); + +export const recursiveNodeDataParser = recursive0DataParser; \ No newline at end of file diff --git a/integration/toDataParser/__snapshots__/withIdentifier.gen.ts b/integration/toDataParser/__snapshots__/withIdentifier.gen.ts new file mode 100644 index 0000000..96ae7f1 --- /dev/null +++ b/integration/toDataParser/__snapshots__/withIdentifier.gen.ts @@ -0,0 +1,10 @@ +import * as DP from "@duplojs/utils/dataParser"; + +export const userRoleDataParser = DP.literal(["admin", "editor", "viewer"]); + +export const userDataParser = DP.object({ + id: DP.templateLiteral(["user-", DP.number(), "-db1"]), + name: DP.string({ checkers: [DP.checkerStringMin(2)] }), + email: DP.string({ checkers: [DP.checkerEmail()] }), + role: userRoleDataParser +}); diff --git a/integration/toDataParser/index.test.ts b/integration/toDataParser/index.test.ts new file mode 100644 index 0000000..3d93de2 --- /dev/null +++ b/integration/toDataParser/index.test.ts @@ -0,0 +1,131 @@ +import { asserts, DPE, E, Path, S, unwrap } from "@duplojs/utils"; +import { SF } from "@duplojs/server-utils"; +import { defaultCheckerTransformers, defaultTransformers, render } from "@duplojs/data-parser-tools/toDataParser"; +import { defaultTransformers as tsDefaultTransformers } from "@duplojs/data-parser-tools/toTypescript"; + +async function typedSnapshot(value: string, filePath: string) { + const absoluteFilePath = Path.resolveRelative([import.meta.dirname, filePath]); + + const exist = await SF.exists(absoluteFilePath); + + if (E.isLeft(exist)) { + const writeTextFileResult = await SF.writeTextFile(absoluteFilePath, value); + asserts(writeTextFileResult, E.isRight); + } + + const expected = await E.rightAsyncPipe( + absoluteFilePath, + SF.readTextFile, + S.trim, + ); + + asserts(expected, E.isRight); + + expect(value).toBe(unwrap(expected)); +} + +describe("integration", () => { + it("basic", async() => { + const userDataParser = DPE.object({ + id: DPE.templateLiteral(["user-", DPE.number(), "-db1"]), + name: DPE.string().min(2), + email: DPE.email(), + role: DPE.literal(["admin", "editor", "viewer"]), + age: DPE + .coerce + .number() + .min(0) + .max(80) + .optional(), + contact: DPE.union([ + DPE.object({ + phone: DPE.string().regex(/^[+\d][\d\s-]{5,}$/), + }), + DPE.object({ + email: DPE.email(), + }), + ]), + address: DPE.object({ + street: DPE.string(), + city: DPE.string(), + zip: DPE.string().regex(/^\d{5}$/), + country: DPE.literal("FR"), + }), + roles: DPE.literal(["admin", "editor", "viewer"]).array().min(1), + preferences: DPE.object({ + theme: DPE.literal(["light", "dark"]), + newsletter: DPE.boolean(), + }).nullable(), + metadata: DPE.record(DPE.string(), DPE.string()), + location: DPE.tuple([DPE.number(), DPE.number()], { rest: DPE.number() }), + createdAt: DPE.date({ coerce: true }), + startAt: DPE.time(), + }); + + const result = render( + userDataParser, + { + identifier: "user", + dataParserTransformers: defaultTransformers, + checkerTransformers: defaultCheckerTransformers, + typescriptTransformers: tsDefaultTransformers, + }, + ); + + await typedSnapshot(result, "__snapshots__/basic.gen.ts"); + }); + + it("recursive", async() => { + const commentSchema: DPE.DataParser = DPE.object({ + id: DPE.string(), + replies: DPE.lazy(() => commentSchema).array(), + }); + + const metaSchema: DPE.DataParser = DPE.tuple([ + DPE.string(), + DPE.lazy(() => metaSchema).array(), + ]); + + const nodeSchema: DPE.DataParser = DPE.object({ + name: DPE.string(), + children: DPE.lazy(() => nodeSchema).array(), + comment: DPE.lazy(() => commentSchema), + meta: DPE.lazy(() => metaSchema), + }); + + const result = render( + nodeSchema, + { + identifier: "recursiveNode", + dataParserTransformers: defaultTransformers, + checkerTransformers: defaultCheckerTransformers, + typescriptTransformers: tsDefaultTransformers, + }, + ); + + await typedSnapshot(result, "__snapshots__/recursive.gen.ts"); + }); + + it("divide with identifier", async() => { + const userRoleDataParser = DPE.literal(["admin", "editor", "viewer"]).addIdentifier("userRole"); + + const userDataParser = DPE.object({ + id: DPE.templateLiteral(["user-", DPE.number(), "-db1"]), + name: DPE.string().min(2), + email: DPE.email(), + role: userRoleDataParser, + }); + + const result = render( + userDataParser, + { + identifier: "user", + dataParserTransformers: defaultTransformers, + checkerTransformers: defaultCheckerTransformers, + typescriptTransformers: tsDefaultTransformers, + }, + ); + + await typedSnapshot(result, "__snapshots__/withIdentifier.gen.ts"); + }); +}); diff --git a/integration/toTypescript/__snapshots__/index.test.ts.snap b/integration/toTypescript/__snapshots__/index.test.ts.snap index e038bde..e6c088f 100644 --- a/integration/toTypescript/__snapshots__/index.test.ts.snap +++ b/integration/toTypescript/__snapshots__/index.test.ts.snap @@ -1,7 +1,7 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html exports[`integration 1`] = ` -"import type { TheDate, TheTime } from "@duplojs/utils/date"; +"import { TheDate, TheTime } from "@duplojs/utils/date"; export type UserProfile = { id: \`user-\${number}-db1\`; diff --git a/package-lock.json b/package-lock.json index 0474a28..2d40939 100644 --- a/package-lock.json +++ b/package-lock.json @@ -39,7 +39,7 @@ }, "peerDependencies": { "@duplojs/server-utils": ">=0.3.0 < 1.0.0", - "@duplojs/utils": ">=1.8.1 <2.0.0" + "@duplojs/utils": ">=1.8.4 <2.0.0" } }, "docs": {}, @@ -980,9 +980,9 @@ } }, "node_modules/@duplojs/utils": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/@duplojs/utils/-/utils-1.8.1.tgz", - "integrity": "sha512-dVsvAPOTCsayWSr2HDA/2XebK35NVsB8efnd3u5Tw+aqSUB09/S4uaqBNCUkkaVQW/S0OTc5OlHGxnTWsPz00A==", + "version": "1.8.4", + "resolved": "https://registry.npmjs.org/@duplojs/utils/-/utils-1.8.4.tgz", + "integrity": "sha512-56TrQQ5TSok/ScbYZbgwgvj+Jft8aQUE/rLThoUYoqGpu0dE0j06/jBZWqtOOEUjyC/3W1bHH8tmsD3eWKVs2w==", "license": "MIT", "peer": true, "workspaces": [ diff --git a/package.json b/package.json index e89e5ba..4372c55 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,11 @@ "import": "./dist/toJsonSchema/index.mjs", "require": "./dist/toJsonSchema/index.cjs", "types": "./dist/toJsonSchema/index.d.ts" + }, + "./toDataParser": { + "import": "./dist/toDataParser/index.mjs", + "require": "./dist/toDataParser/index.cjs", + "types": "./dist/toDataParser/index.d.ts" } }, "files": [ @@ -71,7 +76,7 @@ "vitest": "3.2.4" }, "peerDependencies": { - "@duplojs/utils": ">=1.8.1 <2.0.0", + "@duplojs/utils": ">=1.8.4 <2.0.0", "@duplojs/server-utils": ">=0.3.0 < 1.0.0" }, "dependencies": { diff --git a/scripts/index.ts b/scripts/index.ts index 57f1224..87b45e0 100644 --- a/scripts/index.ts +++ b/scripts/index.ts @@ -1,4 +1,5 @@ export * as DataParserToTypescript from "./toTypescript"; export * as DataParserToJsonSchema from "./toJsonSchema"; +export * as DataParserToDataParser from "./toDataParser"; export * from "./utils"; diff --git a/scripts/toDataParser/buildContext.ts b/scripts/toDataParser/buildContext.ts new file mode 100644 index 0000000..9cca5c8 --- /dev/null +++ b/scripts/toDataParser/buildContext.ts @@ -0,0 +1,109 @@ +import { DP, E, unwrap } from "@duplojs/utils"; +import { createIdentifier, type createTransformer, transformer, type MapContext, type TransformerHook, type DataParserErrorEither, type DataParserNotSupportedEither, type DataParserGetDefinitionErrorEither, type ToTypescriptDataParserErrorEither, type ToTypescriptDataParserNotSupportedEither, type DependenciesContext } from "./dataParserTransformer"; +import type * as TST from "@scripts/toTypescript"; +import { getRecursiveDataParser } from "@scripts/utils"; +import { factory } from "typescript"; +import { type createCheckerTransformer } from "./checkerTransformer"; + +export type ImportMode = "lite" | "extended"; + +export interface BuildedContext { + readonly context: MapContext; + readonly typescriptContext: TST.MapContext; + readonly importContext: TST.MapImportContext; + readonly importMode: ImportMode; +} + +export interface BuildContextParams { + readonly identifier: string; + readonly dataParserTransformers: readonly ReturnType[]; + readonly checkerTransformers: readonly ReturnType[]; + readonly typescriptTransformers: readonly ReturnType[]; + readonly context?: MapContext; + readonly typescriptContext?: TST.MapContext; + readonly importContext?: TST.MapImportContext; + + readonly importMode?: ImportMode; + readonly hooks?: readonly TransformerHook[]; + readonly toTypescript?: { + readonly mode?: TST.TransformerMode; + readonly hooks?: readonly TST.TransformerHook[]; + }; +} + +export function buildContext( + schema: DP.DataParser, + params: BuildContextParams, +): ( + | E.Success + | DataParserNotSupportedEither + | DataParserErrorEither + | DataParserGetDefinitionErrorEither + | ToTypescriptDataParserNotSupportedEither + | ToTypescriptDataParserErrorEither + ) { + const context: MapContext = params.context ?? new Map(); + const typescriptContext: TST.MapContext = params.typescriptContext ?? new Map(); + const importContext: TST.MapImportContext = params.importContext ?? new Map(); + const dependenciesContext: DependenciesContext = new Set(); + const importMode = params.importMode ?? "lite"; + + importContext.set("@duplojs/utils/dataParser", { + namespace: ["DP"], + }); + + if (importMode === "extended") { + importContext.set("@duplojs/utils/dataParserExtended", { + namespace: ["DPE"], + }); + } + + const result = transformer( + schema, + { + ...params, + context, + typescriptContext, + importContext: importContext, + hooks: params.hooks ?? [], + recursiveDataParsers: getRecursiveDataParser(schema), + dependencyIdentifier: factory.createIdentifier(importMode === "extended" ? "DPE" : "DP"), + dependenciesContext, + }, + ); + + if (E.isLeft(result)) { + return result; + } + + if (!schema.definition.identifier) { + context.set( + DP.empty(), + { + identifier: factory.createIdentifier(createIdentifier(params.identifier)), + expression: unwrap(result), + typeIdentifier: null, + dependencies: dependenciesContext, + }, + ); + } else if (schema.definition.identifier !== params.identifier) { + dependenciesContext.add(schema); + + context.set( + DP.empty(), + { + identifier: factory.createIdentifier(createIdentifier(params.identifier)), + expression: factory.createIdentifier(createIdentifier(schema.definition.identifier)), + typeIdentifier: null, + dependencies: dependenciesContext, + }, + ); + } + + return E.success({ + context, + importContext: importContext, + typescriptContext, + importMode, + }); +} diff --git a/scripts/toDataParser/checkerTransformer/create.ts b/scripts/toDataParser/checkerTransformer/create.ts new file mode 100644 index 0000000..c9156d1 --- /dev/null +++ b/scripts/toDataParser/checkerTransformer/create.ts @@ -0,0 +1,74 @@ +import type { CallExpression, ObjectLiteralExpression, PropertyAssignment } from "typescript"; +import { type DP, E } from "@duplojs/utils"; +import type * as TST from "@scripts/toTypescript"; + +export type CheckerTransformerSuccessEither = E.Right<"buildSuccess", CallExpression>; + +export type CheckerTransformerCheckerNotSupportedEither = E.Left<"checkerNotSupport", DP.DataParserChecker>; + +export type CheckerTransformerBuildErrorEither = E.Left<"buildCheckerError", DP.DataParserChecker>; + +export type CheckerTransformerEither = + | CheckerTransformerSuccessEither + | CheckerTransformerCheckerNotSupportedEither + | CheckerTransformerBuildErrorEither; + +export interface CheckerTransformerParams { + readonly importContext: TST.MapImportContext; + + success( + result: CallExpression, + ): CheckerTransformerSuccessEither; + buildError(): CheckerTransformerBuildErrorEither; + addImport(path: string, typeName: string, type?: "default" | "namespace" | "direct"): void; + getDefinition( + customProperties?: readonly PropertyAssignment[] + ): readonly [ObjectLiteralExpression] | readonly []; +} + +/** + * @deprecated + */ +export type DataParserCheckers = ( + | DP.DataParserChecker + | DP.DataParserCheckerArrayMax + | DP.DataParserCheckerArrayMin + | DP.DataParserCheckerBigIntMax + | DP.DataParserCheckerBigIntMin + | DP.DataParserCheckerNumberMax + | DP.DataParserCheckerNumberMin + | DP.DataParserCheckerInt + | DP.DataParserCheckerStringMax + | DP.DataParserCheckerStringMin + | DP.DataParserCheckerEmail + | DP.DataParserCheckerRegex + | DP.DataParserCheckerUrl + | DP.DataParserCheckerUuid + | DP.DataParserCheckerRefine + | DP.DataParserCheckerTimeMin + | DP.DataParserCheckerTimeMax +); + +export type CheckerTransformerBuildFunction< + GenericChecker extends DataParserCheckers = DataParserCheckers, +> = ( + checker: GenericChecker, + params: CheckerTransformerParams, +) => CheckerTransformerEither; + +export function createCheckerTransformer< + GenericChecker extends DataParserCheckers, +>( + support: (checker: DataParserCheckers) => checker is GenericChecker, + builder: CheckerTransformerBuildFunction, +) { + return ( + checker: DataParserCheckers, + params: CheckerTransformerParams, + ): CheckerTransformerEither => support(checker) + ? builder( + checker as GenericChecker, + params, + ) + : E.left("checkerNotSupport", checker); +} diff --git a/scripts/toDataParser/checkerTransformer/defaults/array/index.ts b/scripts/toDataParser/checkerTransformer/defaults/array/index.ts new file mode 100644 index 0000000..cc0a179 --- /dev/null +++ b/scripts/toDataParser/checkerTransformer/defaults/array/index.ts @@ -0,0 +1,2 @@ +export * from "./max"; +export * from "./min"; diff --git a/scripts/toDataParser/checkerTransformer/defaults/array/max.ts b/scripts/toDataParser/checkerTransformer/defaults/array/max.ts new file mode 100644 index 0000000..2a76487 --- /dev/null +++ b/scripts/toDataParser/checkerTransformer/defaults/array/max.ts @@ -0,0 +1,28 @@ +import { DP } from "@duplojs/utils"; +import { createCheckerTransformer } from "../../create"; +import { factory } from "typescript"; + +export const checkerArrayMaxTransformer = createCheckerTransformer( + DP.checkerArrayMaxKind.has, + ( + checker, + { + success, + getDefinition, + }, + ) => { + const expression = factory.createCallExpression( + factory.createPropertyAccessExpression( + factory.createIdentifier("DP"), + factory.createIdentifier("checkerArrayMax"), + ), + undefined, + [ + factory.createNumericLiteral(checker.definition.max), + ...getDefinition(), + ], + ); + + return success(expression); + }, +); diff --git a/scripts/toDataParser/checkerTransformer/defaults/array/min.ts b/scripts/toDataParser/checkerTransformer/defaults/array/min.ts new file mode 100644 index 0000000..43c5384 --- /dev/null +++ b/scripts/toDataParser/checkerTransformer/defaults/array/min.ts @@ -0,0 +1,28 @@ +import { DP } from "@duplojs/utils"; +import { createCheckerTransformer } from "../../create"; +import { factory } from "typescript"; + +export const checkerArrayMinTransformer = createCheckerTransformer( + DP.checkerArrayMinKind.has, + ( + checker, + { + success, + getDefinition, + }, + ) => { + const expression = factory.createCallExpression( + factory.createPropertyAccessExpression( + factory.createIdentifier("DP"), + factory.createIdentifier("checkerArrayMin"), + ), + undefined, + [ + factory.createNumericLiteral(checker.definition.min), + ...getDefinition(), + ], + ); + + return success(expression); + }, +); diff --git a/scripts/toDataParser/checkerTransformer/defaults/bigint/index.ts b/scripts/toDataParser/checkerTransformer/defaults/bigint/index.ts new file mode 100644 index 0000000..cc0a179 --- /dev/null +++ b/scripts/toDataParser/checkerTransformer/defaults/bigint/index.ts @@ -0,0 +1,2 @@ +export * from "./max"; +export * from "./min"; diff --git a/scripts/toDataParser/checkerTransformer/defaults/bigint/max.ts b/scripts/toDataParser/checkerTransformer/defaults/bigint/max.ts new file mode 100644 index 0000000..0be6bf9 --- /dev/null +++ b/scripts/toDataParser/checkerTransformer/defaults/bigint/max.ts @@ -0,0 +1,28 @@ +import { DP } from "@duplojs/utils"; +import { createCheckerTransformer } from "../../create"; +import { factory } from "typescript"; + +export const checkerBigIntMaxTransformer = createCheckerTransformer( + DP.checkerBigIntMaxKind.has, + ( + checker, + { + success, + getDefinition, + }, + ) => { + const expression = factory.createCallExpression( + factory.createPropertyAccessExpression( + factory.createIdentifier("DP"), + factory.createIdentifier("checkerBigIntMax"), + ), + undefined, + [ + factory.createBigIntLiteral(`${checker.definition.max.toString()}n`), + ...getDefinition(), + ], + ); + + return success(expression); + }, +); diff --git a/scripts/toDataParser/checkerTransformer/defaults/bigint/min.ts b/scripts/toDataParser/checkerTransformer/defaults/bigint/min.ts new file mode 100644 index 0000000..98c8693 --- /dev/null +++ b/scripts/toDataParser/checkerTransformer/defaults/bigint/min.ts @@ -0,0 +1,28 @@ +import { DP } from "@duplojs/utils"; +import { createCheckerTransformer } from "../../create"; +import { factory } from "typescript"; + +export const checkerBigIntMinTransformer = createCheckerTransformer( + DP.checkerBigIntMinKind.has, + ( + checker, + { + success, + getDefinition, + }, + ) => { + const expression = factory.createCallExpression( + factory.createPropertyAccessExpression( + factory.createIdentifier("DP"), + factory.createIdentifier("checkerBigIntMin"), + ), + undefined, + [ + factory.createBigIntLiteral(`${checker.definition.min.toString()}n`), + ...getDefinition(), + ], + ); + + return success(expression); + }, +); diff --git a/scripts/toDataParser/checkerTransformer/defaults/index.ts b/scripts/toDataParser/checkerTransformer/defaults/index.ts new file mode 100644 index 0000000..457426e --- /dev/null +++ b/scripts/toDataParser/checkerTransformer/defaults/index.ts @@ -0,0 +1,36 @@ +export * from "./number"; +export * from "./string"; +export * from "./array"; + +import type { createCheckerTransformer } from "../create"; + +import { checkerIntTransformer, checkerNumberMaxTransformer, checkerNumberMinTransformer } from "./number"; +import { checkerEmailTransformer, checkerRegexTransformer, checkerStringMaxTransformer, checkerStringMinTransformer, checkerUrlTransformer, checkerUuidTransformer } from "./string"; +import { checkerArrayMaxTransformer, checkerArrayMinTransformer } from "./array"; +import { checkerBigIntMaxTransformer, checkerBigIntMinTransformer } from "./bigint"; +import { checkerTimeMaxTransformer, checkerTimeMinTransformer } from "./time"; +import { checkerRefineTransformer } from "./refine"; + +export const defaultCheckerTransformers = [ + checkerRefineTransformer, + // number + checkerNumberMaxTransformer, + checkerNumberMinTransformer, + checkerIntTransformer, + // string + checkerStringMaxTransformer, + checkerStringMinTransformer, + checkerEmailTransformer, + checkerRegexTransformer, + checkerUuidTransformer, + checkerUrlTransformer, + // array + checkerArrayMaxTransformer, + checkerArrayMinTransformer, + // bigint + checkerBigIntMaxTransformer, + checkerBigIntMinTransformer, + // time + checkerTimeMaxTransformer, + checkerTimeMinTransformer, +] as const satisfies readonly ReturnType[]; diff --git a/scripts/toDataParser/checkerTransformer/defaults/number/index.ts b/scripts/toDataParser/checkerTransformer/defaults/number/index.ts new file mode 100644 index 0000000..a077ac2 --- /dev/null +++ b/scripts/toDataParser/checkerTransformer/defaults/number/index.ts @@ -0,0 +1,3 @@ +export * from "./int"; +export * from "./min"; +export * from "./max"; diff --git a/scripts/toDataParser/checkerTransformer/defaults/number/int.ts b/scripts/toDataParser/checkerTransformer/defaults/number/int.ts new file mode 100644 index 0000000..2e875d5 --- /dev/null +++ b/scripts/toDataParser/checkerTransformer/defaults/number/int.ts @@ -0,0 +1,25 @@ +import { DP } from "@duplojs/utils"; +import { createCheckerTransformer } from "../../create"; +import { factory } from "typescript"; + +export const checkerIntTransformer = createCheckerTransformer( + DP.checkerIntKind.has, + ( + checker, + { + success, + getDefinition, + }, + ) => { + const expression = factory.createCallExpression( + factory.createPropertyAccessExpression( + factory.createIdentifier("DP"), + factory.createIdentifier("checkerInt"), + ), + undefined, + getDefinition(), + ); + + return success(expression); + }, +); diff --git a/scripts/toDataParser/checkerTransformer/defaults/number/max.ts b/scripts/toDataParser/checkerTransformer/defaults/number/max.ts new file mode 100644 index 0000000..5c2939b --- /dev/null +++ b/scripts/toDataParser/checkerTransformer/defaults/number/max.ts @@ -0,0 +1,39 @@ +import { DP } from "@duplojs/utils"; +import { createCheckerTransformer } from "../../create"; +import { factory, type PropertyAssignment } from "typescript"; + +export const checkerNumberMaxTransformer = createCheckerTransformer( + DP.checkerNumberMaxKind.has, + ( + checker, + { + success, + getDefinition, + }, + ) => { + const checkerDefinition: PropertyAssignment[] = []; + + if (checker.definition.exclusive) { + checkerDefinition.push( + factory.createPropertyAssignment( + factory.createIdentifier("exclusive"), + factory.createTrue(), + ), + ); + } + + const expression = factory.createCallExpression( + factory.createPropertyAccessExpression( + factory.createIdentifier("DP"), + factory.createIdentifier("checkerNumberMax"), + ), + undefined, + [ + factory.createNumericLiteral(checker.definition.max), + ...getDefinition(checkerDefinition), + ], + ); + + return success(expression); + }, +); diff --git a/scripts/toDataParser/checkerTransformer/defaults/number/min.ts b/scripts/toDataParser/checkerTransformer/defaults/number/min.ts new file mode 100644 index 0000000..3fef8c7 --- /dev/null +++ b/scripts/toDataParser/checkerTransformer/defaults/number/min.ts @@ -0,0 +1,39 @@ +import { DP } from "@duplojs/utils"; +import { createCheckerTransformer } from "../../create"; +import { factory, type PropertyAssignment } from "typescript"; + +export const checkerNumberMinTransformer = createCheckerTransformer( + DP.checkerNumberMinKind.has, + ( + checker, + { + success, + getDefinition, + }, + ) => { + const checkerDefinition: PropertyAssignment[] = []; + + if (checker.definition.exclusive) { + checkerDefinition.push( + factory.createPropertyAssignment( + factory.createIdentifier("exclusive"), + factory.createTrue(), + ), + ); + } + + const expression = factory.createCallExpression( + factory.createPropertyAccessExpression( + factory.createIdentifier("DP"), + factory.createIdentifier("checkerNumberMin"), + ), + undefined, + [ + factory.createNumericLiteral(checker.definition.min), + ...getDefinition(checkerDefinition), + ], + ); + + return success(expression); + }, +); diff --git a/scripts/toDataParser/checkerTransformer/defaults/refine.ts b/scripts/toDataParser/checkerTransformer/defaults/refine.ts new file mode 100644 index 0000000..e4ceab1 --- /dev/null +++ b/scripts/toDataParser/checkerTransformer/defaults/refine.ts @@ -0,0 +1,61 @@ +import { DP } from "@duplojs/utils"; +import { createCheckerTransformer } from "../create"; +import { createSourceFile, factory, isArrowFunction, isExpressionStatement, isFunctionExpression, isParenthesizedExpression, ScriptTarget } from "typescript"; + +export const checkerRefineTransformer = createCheckerTransformer( + DP.dataParserCheckerRefineKind.has, + ( + checker, + { + success, + buildError, + getDefinition, + }, + ) => { + const functionSource = checker.definition.theFunction.toString(); + + if (functionSource.includes("[native code]")) { + return buildError(); + } + + const sourceFile = createSourceFile( + "refine-function.ts", + `(${functionSource})`, + ScriptTarget.Latest, + false, + ); + + const statement = sourceFile.statements[0]; + if (!statement || !isExpressionStatement(statement)) { + return buildError(); + } + + const functionExpression = isParenthesizedExpression(statement.expression) + ? statement.expression.expression + : undefined; + + if ( + !functionExpression + || ( + !isFunctionExpression(functionExpression) + && !isArrowFunction(functionExpression) + ) + ) { + return buildError(); + } + + const expression = factory.createCallExpression( + factory.createPropertyAccessExpression( + factory.createIdentifier("DP"), + factory.createIdentifier("checkerRefine"), + ), + undefined, + [ + functionExpression, + ...getDefinition(), + ], + ); + + return success(expression); + }, +); diff --git a/scripts/toDataParser/checkerTransformer/defaults/string/email.ts b/scripts/toDataParser/checkerTransformer/defaults/string/email.ts new file mode 100644 index 0000000..9f7bd25 --- /dev/null +++ b/scripts/toDataParser/checkerTransformer/defaults/string/email.ts @@ -0,0 +1,25 @@ +import { DP } from "@duplojs/utils"; +import { createCheckerTransformer } from "../../create"; +import { factory } from "typescript"; + +export const checkerEmailTransformer = createCheckerTransformer( + DP.checkerEmailKind.has, + ( + checker, + { + success, + getDefinition, + }, + ) => { + const expression = factory.createCallExpression( + factory.createPropertyAccessExpression( + factory.createIdentifier("DP"), + factory.createIdentifier("checkerEmail"), + ), + undefined, + getDefinition(), + ); + + return success(expression); + }, +); diff --git a/scripts/toDataParser/checkerTransformer/defaults/string/index.ts b/scripts/toDataParser/checkerTransformer/defaults/string/index.ts new file mode 100644 index 0000000..7713ea4 --- /dev/null +++ b/scripts/toDataParser/checkerTransformer/defaults/string/index.ts @@ -0,0 +1,6 @@ +export * from "./email"; +export * from "./max"; +export * from "./min"; +export * from "./regex"; +export * from "./url"; +export * from "./uuid"; diff --git a/scripts/toDataParser/checkerTransformer/defaults/string/max.ts b/scripts/toDataParser/checkerTransformer/defaults/string/max.ts new file mode 100644 index 0000000..660d4ae --- /dev/null +++ b/scripts/toDataParser/checkerTransformer/defaults/string/max.ts @@ -0,0 +1,28 @@ +import { DP } from "@duplojs/utils"; +import { createCheckerTransformer } from "../../create"; +import { factory } from "typescript"; + +export const checkerStringMaxTransformer = createCheckerTransformer( + DP.checkerStringMaxKind.has, + ( + checker, + { + success, + getDefinition, + }, + ) => { + const expression = factory.createCallExpression( + factory.createPropertyAccessExpression( + factory.createIdentifier("DP"), + factory.createIdentifier("checkerStringMax"), + ), + undefined, + [ + factory.createNumericLiteral(checker.definition.max), + ...getDefinition(), + ], + ); + + return success(expression); + }, +); diff --git a/scripts/toDataParser/checkerTransformer/defaults/string/min.ts b/scripts/toDataParser/checkerTransformer/defaults/string/min.ts new file mode 100644 index 0000000..f840880 --- /dev/null +++ b/scripts/toDataParser/checkerTransformer/defaults/string/min.ts @@ -0,0 +1,28 @@ +import { DP } from "@duplojs/utils"; +import { createCheckerTransformer } from "../../create"; +import { factory } from "typescript"; + +export const checkerStringMinTransformer = createCheckerTransformer( + DP.checkerStringMinKind.has, + ( + checker, + { + success, + getDefinition, + }, + ) => { + const expression = factory.createCallExpression( + factory.createPropertyAccessExpression( + factory.createIdentifier("DP"), + factory.createIdentifier("checkerStringMin"), + ), + undefined, + [ + factory.createNumericLiteral(checker.definition.min), + ...getDefinition(), + ], + ); + + return success(expression); + }, +); diff --git a/scripts/toDataParser/checkerTransformer/defaults/string/regex.ts b/scripts/toDataParser/checkerTransformer/defaults/string/regex.ts new file mode 100644 index 0000000..ca0346e --- /dev/null +++ b/scripts/toDataParser/checkerTransformer/defaults/string/regex.ts @@ -0,0 +1,32 @@ +import { DP } from "@duplojs/utils"; +import { createCheckerTransformer } from "../../create"; +import { factory } from "typescript"; + +export const checkerRegexTransformer = createCheckerTransformer( + DP.checkerRegexKind.has, + ( + checker, + { + success, + getDefinition, + }, + ) => { + const expression = factory.createCallExpression( + factory.createPropertyAccessExpression( + factory.createIdentifier("DP"), + factory.createIdentifier("checkerRegex"), + ), + undefined, + [ + factory.createRegularExpressionLiteral( + `/${checker.definition.regex.source}/${checker.definition.regex.flags}`, + ), + ...getDefinition(), + ], + ); + + return success(expression); + }, +); + +DP.checkerRegex(/^/); diff --git a/scripts/toDataParser/checkerTransformer/defaults/string/url.ts b/scripts/toDataParser/checkerTransformer/defaults/string/url.ts new file mode 100644 index 0000000..f6cfc16 --- /dev/null +++ b/scripts/toDataParser/checkerTransformer/defaults/string/url.ts @@ -0,0 +1,58 @@ +import { DP } from "@duplojs/utils"; +import { createCheckerTransformer } from "../../create"; +import { factory, type PropertyAssignment } from "typescript"; + +export const checkerUrlTransformer = createCheckerTransformer( + DP.checkerUrlKind.has, + ( + checker, + { + success, + getDefinition, + }, + ) => { + const checkerDefinition: PropertyAssignment[] = []; + + if (checker.definition.hostname) { + checkerDefinition.push( + factory.createPropertyAssignment( + factory.createIdentifier("hostname"), + factory.createRegularExpressionLiteral( + `/${checker.definition.hostname.source}/${checker.definition.hostname.flags}`, + ), + ), + ); + } + + if (checker.definition.normalize) { + checkerDefinition.push( + factory.createPropertyAssignment( + factory.createIdentifier("normalize"), + factory.createTrue(), + ), + ); + } + + if (checker.definition.protocol) { + checkerDefinition.push( + factory.createPropertyAssignment( + factory.createIdentifier("protocol"), + factory.createRegularExpressionLiteral( + `/${checker.definition.protocol.source}/${checker.definition.protocol.flags}`, + ), + ), + ); + } + + const expression = factory.createCallExpression( + factory.createPropertyAccessExpression( + factory.createIdentifier("DP"), + factory.createIdentifier("checkerUrl"), + ), + undefined, + getDefinition(checkerDefinition), + ); + + return success(expression); + }, +); diff --git a/scripts/toDataParser/checkerTransformer/defaults/string/uuid.ts b/scripts/toDataParser/checkerTransformer/defaults/string/uuid.ts new file mode 100644 index 0000000..a488c41 --- /dev/null +++ b/scripts/toDataParser/checkerTransformer/defaults/string/uuid.ts @@ -0,0 +1,25 @@ +import { DP } from "@duplojs/utils"; +import { createCheckerTransformer } from "../../create"; +import { factory } from "typescript"; + +export const checkerUuidTransformer = createCheckerTransformer( + DP.checkerUuidKind.has, + ( + checker, + { + success, + getDefinition, + }, + ) => { + const expression = factory.createCallExpression( + factory.createPropertyAccessExpression( + factory.createIdentifier("DP"), + factory.createIdentifier("checkerUuid"), + ), + undefined, + getDefinition(), + ); + + return success(expression); + }, +); diff --git a/scripts/toDataParser/checkerTransformer/defaults/time/index.ts b/scripts/toDataParser/checkerTransformer/defaults/time/index.ts new file mode 100644 index 0000000..cc0a179 --- /dev/null +++ b/scripts/toDataParser/checkerTransformer/defaults/time/index.ts @@ -0,0 +1,2 @@ +export * from "./max"; +export * from "./min"; diff --git a/scripts/toDataParser/checkerTransformer/defaults/time/max.ts b/scripts/toDataParser/checkerTransformer/defaults/time/max.ts new file mode 100644 index 0000000..3f4f862 --- /dev/null +++ b/scripts/toDataParser/checkerTransformer/defaults/time/max.ts @@ -0,0 +1,41 @@ +import { DP } from "@duplojs/utils"; +import { createCheckerTransformer } from "../../create"; +import { factory } from "typescript"; + +export const checkerTimeMaxTransformer = createCheckerTransformer( + DP.checkerTimeMaxKind.has, + ( + checker, + { + success, + getDefinition, + addImport, + }, + ) => { + addImport("@duplojs/utils/date", "D", "namespace"); + + const expression = factory.createCallExpression( + factory.createPropertyAccessExpression( + factory.createIdentifier("DP"), + factory.createIdentifier("checkerTimeMax"), + ), + undefined, + [ + factory.createCallExpression( + factory.createPropertyAccessExpression( + factory.createIdentifier("D"), + factory.createIdentifier("createTime"), + ), + undefined, + [ + factory.createNumericLiteral(checker.definition.max.toNative()), + factory.createStringLiteral("millisecond"), + ], + ), + ...getDefinition(), + ], + ); + + return success(expression); + }, +); diff --git a/scripts/toDataParser/checkerTransformer/defaults/time/min.ts b/scripts/toDataParser/checkerTransformer/defaults/time/min.ts new file mode 100644 index 0000000..e4cd808 --- /dev/null +++ b/scripts/toDataParser/checkerTransformer/defaults/time/min.ts @@ -0,0 +1,41 @@ +import { DP } from "@duplojs/utils"; +import { createCheckerTransformer } from "../../create"; +import { factory } from "typescript"; + +export const checkerTimeMinTransformer = createCheckerTransformer( + DP.checkerTimeMinKind.has, + ( + checker, + { + success, + getDefinition, + addImport, + }, + ) => { + addImport("@duplojs/utils/date", "D", "namespace"); + + const expression = factory.createCallExpression( + factory.createPropertyAccessExpression( + factory.createIdentifier("DP"), + factory.createIdentifier("checkerTimeMin"), + ), + undefined, + [ + factory.createCallExpression( + factory.createPropertyAccessExpression( + factory.createIdentifier("D"), + factory.createIdentifier("createTime"), + ), + undefined, + [ + factory.createNumericLiteral(checker.definition.min.toNative()), + factory.createStringLiteral("millisecond"), + ], + ), + ...getDefinition(), + ], + ); + + return success(expression); + }, +); diff --git a/scripts/toDataParser/checkerTransformer/index.ts b/scripts/toDataParser/checkerTransformer/index.ts new file mode 100644 index 0000000..a51ba64 --- /dev/null +++ b/scripts/toDataParser/checkerTransformer/index.ts @@ -0,0 +1,3 @@ +export * from "./create"; +export * from "./transformer"; +export * from "./defaults"; diff --git a/scripts/toDataParser/checkerTransformer/transformer.ts b/scripts/toDataParser/checkerTransformer/transformer.ts new file mode 100644 index 0000000..337e361 --- /dev/null +++ b/scripts/toDataParser/checkerTransformer/transformer.ts @@ -0,0 +1,74 @@ +import { A, E, type DP } from "@duplojs/utils"; +import type { CheckerTransformerParams, createCheckerTransformer, CheckerTransformerEither } from "./create"; +import { factory, type PropertyAssignment } from "typescript"; +import * as TST from "@scripts/toTypescript"; + +export interface CheckerTransformerFunctionParams { + readonly transformers: readonly ReturnType[]; + readonly importContext: TST.MapImportContext; +} + +export function getCheckerDefinition(checker: DP.DataParserChecker, customProperties?: readonly PropertyAssignment[]) { + const propertyAssignments: PropertyAssignment[] = []; + + if (checker.definition.errorMessage) { + propertyAssignments.push( + factory.createPropertyAssignment( + factory.createIdentifier("errorMessage"), + factory.createStringLiteral(checker.definition.errorMessage), + ), + ); + } + + if (customProperties) { + propertyAssignments.push(...customProperties); + } + + return A.minElements(propertyAssignments, 1) + ? [factory.createObjectLiteralExpression(propertyAssignments)] + : []; +} + +export function checkerTransformer( + checker: DP.DataParserChecker, + params: CheckerTransformerFunctionParams, +) { + const functionParams: CheckerTransformerParams = { + importContext: params.importContext, + success(result) { + return E.right("buildSuccess", result); + }, + buildError() { + return E.left("buildCheckerError", checker); + }, + addImport: TST.createAddImport(params.importContext), + getDefinition(customProperties) { + return getCheckerDefinition(checker, customProperties); + }, + }; + + return A.reduce( + params.transformers, + A.reduceFrom( + E.left("checkerNotSupport", checker), + ), + ({ + element: functionBuilder, + lastValue, + next, + exit, + }) => { + const result = functionBuilder(checker, functionParams); + + if (E.isLeft(result)) { + if (!E.hasInformation(result, "checkerNotSupport")) { + return exit(result); + } + + return next(lastValue); + } + + return exit(result); + }, + ); +} diff --git a/scripts/toDataParser/dataParserTransformer/create.ts b/scripts/toDataParser/dataParserTransformer/create.ts new file mode 100644 index 0000000..0f385f3 --- /dev/null +++ b/scripts/toDataParser/dataParserTransformer/create.ts @@ -0,0 +1,85 @@ +import { type DP, E } from "@duplojs/utils"; +import type * as TST from "@scripts/toTypescript"; +import type { CallExpression, Identifier, ObjectLiteralExpression, PropertyAssignment } from "typescript"; +import type { CheckerTransformerBuildErrorEither, CheckerTransformerCheckerNotSupportedEither } from "../checkerTransformer"; + +export type TransformerSuccessEither = E.Right<"buildSuccess", CallExpression | Identifier>; + +export type DataParserNotSupportedEither = E.Left<"dataParserNotSupport", DP.DataParser>; + +export type DataParserErrorEither = E.Left<"buildDataParserError", DP.DataParser>; + +export type ToTypescriptDataParserNotSupportedEither = E.Left<"toTypescriptDataParserNotSupport", DP.DataParser>; + +export type ToTypescriptDataParserErrorEither = E.Left<"toTypescriptBuildDataParserError", DP.DataParser>; + +export type DataParserGetDefinitionErrorEither = E.Left< + "buildDataParserGetDefinitionError", + { + dataParser: DP.DataParser; + error: CheckerTransformerCheckerNotSupportedEither | CheckerTransformerBuildErrorEither; + } +>; + +export type DependenciesContext = Set; + +export interface MapContextValue { + readonly identifier: Identifier; + readonly expression: CallExpression | Identifier; + readonly typeIdentifier: Identifier | null; + readonly dependencies: DependenciesContext; +} + +export type MapContext = Map; + +export type MaybeTransformerEither = + | TransformerSuccessEither + | DataParserNotSupportedEither + | DataParserErrorEither + | DataParserGetDefinitionErrorEither + | ToTypescriptDataParserNotSupportedEither + | ToTypescriptDataParserErrorEither; + +export interface TransformerParams { + readonly dependencyIdentifier: Identifier; + readonly context: MapContext; + readonly importContext: TST.MapImportContext; + + transformer( + dataParser: DP.DataParser, + ): MaybeTransformerEither; + + success( + result: CallExpression | Identifier, + ): TransformerSuccessEither; + + buildError(): DataParserErrorEither; + addImport(path: string, typeName: string, type?: "default" | "namespace" | "direct"): void; + getDefinition( + customProperties?: readonly PropertyAssignment[], + ): readonly [ObjectLiteralExpression] | readonly [] | DataParserGetDefinitionErrorEither; +} + +export type TransformerBuildFunction< + GenericDataParser extends DP.DataParsers = DP.DataParsers, +> = ( + dataParser: GenericDataParser, + params: TransformerParams, +) => MaybeTransformerEither; + +export function createTransformer< + GenericDataParser extends DP.DataParsers, +>( + support: (dataParser: DP.DataParsers) => dataParser is GenericDataParser, + builder: TransformerBuildFunction, +) { + return ( + dataParser: DP.DataParsers, + params: TransformerParams, + ): MaybeTransformerEither => support(dataParser) + ? builder( + dataParser, + params, + ) + : E.left("dataParserNotSupport", dataParser); +} diff --git a/scripts/toDataParser/dataParserTransformer/createIdentifier.ts b/scripts/toDataParser/dataParserTransformer/createIdentifier.ts new file mode 100644 index 0000000..33a596f --- /dev/null +++ b/scripts/toDataParser/dataParserTransformer/createIdentifier.ts @@ -0,0 +1,5 @@ +import { S } from "@duplojs/utils"; + +export function createIdentifier(identifier: string) { + return `${S.uncapitalize(identifier)}DataParser`; +} diff --git a/scripts/toDataParser/dataParserTransformer/defaults/array.ts b/scripts/toDataParser/dataParserTransformer/defaults/array.ts new file mode 100644 index 0000000..0fb3aa2 --- /dev/null +++ b/scripts/toDataParser/dataParserTransformer/defaults/array.ts @@ -0,0 +1,43 @@ +import { DP, E, pipe, unwrap } from "@duplojs/utils"; +import { createTransformer } from "../create"; +import { factory } from "typescript"; + +export const arrayTransformer = createTransformer( + DP.arrayKind.has, + ( + dataParser, + { + success, + dependencyIdentifier, + getDefinition, + transformer, + }, + ) => { + const element = transformer(dataParser.definition.element); + + if (E.isLeft(element)) { + return element; + } + + const definition = getDefinition(); + + if (E.isLeft(definition)) { + return definition; + } + + return pipe( + factory.createCallExpression( + factory.createPropertyAccessExpression( + dependencyIdentifier, + factory.createIdentifier("array"), + ), + undefined, + [ + unwrap(element), + ...definition, + ], + ), + success, + ); + }, +); diff --git a/scripts/toDataParser/dataParserTransformer/defaults/bigint.ts b/scripts/toDataParser/dataParserTransformer/defaults/bigint.ts new file mode 100644 index 0000000..ddccd28 --- /dev/null +++ b/scripts/toDataParser/dataParserTransformer/defaults/bigint.ts @@ -0,0 +1,42 @@ +import { DP, E, pipe } from "@duplojs/utils"; +import { createTransformer } from "../create"; +import { factory } from "typescript"; + +export const bigIntTransformer = createTransformer( + DP.bigIntKind.has, + ( + dataParser, + { + success, + dependencyIdentifier, + getDefinition, + }, + ) => { + const definition = getDefinition( + dataParser.definition.coerce + ? [ + factory.createPropertyAssignment( + factory.createIdentifier("coerce"), + factory.createTrue(), + ), + ] + : undefined, + ); + + if (E.isLeft(definition)) { + return definition; + } + + return pipe( + factory.createCallExpression( + factory.createPropertyAccessExpression( + dependencyIdentifier, + factory.createIdentifier("bigint"), + ), + undefined, + definition, + ), + success, + ); + }, +); diff --git a/scripts/toDataParser/dataParserTransformer/defaults/boolean.ts b/scripts/toDataParser/dataParserTransformer/defaults/boolean.ts new file mode 100644 index 0000000..cf38b21 --- /dev/null +++ b/scripts/toDataParser/dataParserTransformer/defaults/boolean.ts @@ -0,0 +1,42 @@ +import { DP, E, pipe } from "@duplojs/utils"; +import { createTransformer } from "../create"; +import { factory } from "typescript"; + +export const booleanTransformer = createTransformer( + DP.booleanKind.has, + ( + dataParser, + { + success, + dependencyIdentifier, + getDefinition, + }, + ) => { + const definition = getDefinition( + dataParser.definition.coerce + ? [ + factory.createPropertyAssignment( + factory.createIdentifier("coerce"), + factory.createTrue(), + ), + ] + : undefined, + ); + + if (E.isLeft(definition)) { + return definition; + } + + return pipe( + factory.createCallExpression( + factory.createPropertyAccessExpression( + dependencyIdentifier, + factory.createIdentifier("boolean"), + ), + undefined, + definition, + ), + success, + ); + }, +); diff --git a/scripts/toDataParser/dataParserTransformer/defaults/date.ts b/scripts/toDataParser/dataParserTransformer/defaults/date.ts new file mode 100644 index 0000000..cc3d154 --- /dev/null +++ b/scripts/toDataParser/dataParserTransformer/defaults/date.ts @@ -0,0 +1,42 @@ +import { DP, E, pipe } from "@duplojs/utils"; +import { createTransformer } from "../create"; +import { factory } from "typescript"; + +export const dateTransformer = createTransformer( + DP.dateKind.has, + ( + dataParser, + { + success, + dependencyIdentifier, + getDefinition, + }, + ) => { + const definition = getDefinition( + dataParser.definition.coerce + ? [ + factory.createPropertyAssignment( + factory.createIdentifier("coerce"), + factory.createTrue(), + ), + ] + : undefined, + ); + + if (E.isLeft(definition)) { + return definition; + } + + return pipe( + factory.createCallExpression( + factory.createPropertyAccessExpression( + dependencyIdentifier, + factory.createIdentifier("date"), + ), + undefined, + definition, + ), + success, + ); + }, +); diff --git a/scripts/toDataParser/dataParserTransformer/defaults/empty.ts b/scripts/toDataParser/dataParserTransformer/defaults/empty.ts new file mode 100644 index 0000000..357d8ef --- /dev/null +++ b/scripts/toDataParser/dataParserTransformer/defaults/empty.ts @@ -0,0 +1,42 @@ +import { DP, E, pipe } from "@duplojs/utils"; +import { createTransformer } from "../create"; +import { factory } from "typescript"; + +export const emptyTransformer = createTransformer( + DP.emptyKind.has, + ( + dataParser, + { + success, + dependencyIdentifier, + getDefinition, + }, + ) => { + const definition = getDefinition( + dataParser.definition.coerce + ? [ + factory.createPropertyAssignment( + factory.createIdentifier("coerce"), + factory.createTrue(), + ), + ] + : undefined, + ); + + if (E.isLeft(definition)) { + return definition; + } + + return pipe( + factory.createCallExpression( + factory.createPropertyAccessExpression( + dependencyIdentifier, + factory.createIdentifier("empty"), + ), + undefined, + definition, + ), + success, + ); + }, +); diff --git a/scripts/toDataParser/dataParserTransformer/defaults/file.ts b/scripts/toDataParser/dataParserTransformer/defaults/file.ts new file mode 100644 index 0000000..b8e7692 --- /dev/null +++ b/scripts/toDataParser/dataParserTransformer/defaults/file.ts @@ -0,0 +1,98 @@ +import { A, E, justExec, pipe } from "@duplojs/utils"; +import { createTransformer } from "../create"; +import { factory, type PropertyAssignment } from "typescript"; +import { SDP } from "@duplojs/server-utils"; + +export const fileTransformer = createTransformer( + SDP.fileKind.has, + ( + dataParser, + { + success, + dependencyIdentifier, + getDefinition, + addImport, + }, + ) => { + const definition = getDefinition(); + + if (E.isLeft(definition)) { + return definition; + } + + const dataParserFileParams: PropertyAssignment[] = []; + + if (dataParser.definition.coerce) { + dataParserFileParams.push( + factory.createPropertyAssignment( + factory.createIdentifier("coerce"), + factory.createTrue(), + ), + ); + } + if (dataParser.definition.checkExist) { + dataParserFileParams.push( + factory.createPropertyAssignment( + factory.createIdentifier("checkExist"), + factory.createTrue(), + ), + ); + } + if (dataParser.definition.maxSize) { + dataParserFileParams.push( + factory.createPropertyAssignment( + factory.createIdentifier("maxSize"), + factory.createNumericLiteral(dataParser.definition.maxSize), + ), + ); + } + if (dataParser.definition.minSize) { + dataParserFileParams.push( + factory.createPropertyAssignment( + factory.createIdentifier("minSize"), + factory.createNumericLiteral(dataParser.definition.minSize), + ), + ); + } + if (dataParser.definition.mimeType) { + dataParserFileParams.push( + factory.createPropertyAssignment( + factory.createIdentifier("mimeType"), + factory.createRegularExpressionLiteral(dataParser.definition.mimeType.toString()), + ), + ); + } + + const namespace = dependencyIdentifier.text === "DP" + ? justExec( + () => { + addImport("@duplojs/server-utils/dataParser", "SDP", "namespace"); + return "SDP"; + }, + ) + : justExec( + () => { + addImport("@duplojs/server-utils/dataParserExtended", "SDPE", "namespace"); + return "SDPE"; + }, + ); + + return pipe( + factory.createCallExpression( + factory.createPropertyAccessExpression( + factory.createIdentifier(namespace), + factory.createIdentifier("file"), + ), + undefined, + [ + factory.createObjectLiteralExpression( + dataParserFileParams, + A.minElements(dataParserFileParams, 2), + ), + ...definition, + ], + ), + success, + ); + }, +); diff --git a/scripts/toDataParser/dataParserTransformer/defaults/index.ts b/scripts/toDataParser/dataParserTransformer/defaults/index.ts new file mode 100644 index 0000000..2b30fdc --- /dev/null +++ b/scripts/toDataParser/dataParserTransformer/defaults/index.ts @@ -0,0 +1,74 @@ +export * from "./array"; +export * from "./bigint"; +export * from "./boolean"; +export * from "./date"; +export * from "./empty"; +export * from "./file"; +export * from "./lazy"; +export * from "./literal"; +export * from "./nil"; +export * from "./nullable"; +export * from "./number"; +export * from "./object"; +export * from "./optional"; +export * from "./pipe"; +export * from "./record"; +export * from "./recover"; +export * from "./string"; +export * from "./templateLiteral"; +export * from "./time"; +export * from "./transform"; +export * from "./tuple"; +export * from "./union"; +export * from "./unknown"; + +import type { createTransformer } from "../create"; +import { arrayTransformer } from "./array"; +import { bigIntTransformer } from "./bigint"; +import { booleanTransformer } from "./boolean"; +import { dateTransformer } from "./date"; +import { emptyTransformer } from "./empty"; +import { fileTransformer } from "./file"; +import { lazyTransformer } from "./lazy"; +import { literalTransformer } from "./literal"; +import { nilTransformer } from "./nil"; +import { nullableTransformer } from "./nullable"; +import { numberTransformer } from "./number"; +import { objectTransformer } from "./object"; +import { optionalTransformer } from "./optional"; +import { pipeTransformer } from "./pipe"; +import { recordTransformer } from "./record"; +import { recoverTransformer } from "./recover"; +import { stringTransformer } from "./string"; +import { templateLiteralTransformer } from "./templateLiteral"; +import { timeTransformer } from "./time"; +import { transformTransformer } from "./transform"; +import { tupleTransformer } from "./tuple"; +import { unionTransformer } from "./union"; +import { unknownTransformer } from "./unknown"; + +export const defaultTransformers = [ + arrayTransformer, + bigIntTransformer, + booleanTransformer, + dateTransformer, + emptyTransformer, + fileTransformer, + lazyTransformer, + literalTransformer, + nilTransformer, + nullableTransformer, + numberTransformer, + objectTransformer, + optionalTransformer, + pipeTransformer, + recordTransformer, + recoverTransformer, + stringTransformer, + templateLiteralTransformer, + timeTransformer, + transformTransformer, + tupleTransformer, + unionTransformer, + unknownTransformer, +] as const satisfies readonly ReturnType[]; diff --git a/scripts/toDataParser/dataParserTransformer/defaults/lazy.ts b/scripts/toDataParser/dataParserTransformer/defaults/lazy.ts new file mode 100644 index 0000000..2f85b27 --- /dev/null +++ b/scripts/toDataParser/dataParserTransformer/defaults/lazy.ts @@ -0,0 +1,50 @@ +import { DP, E, pipe, unwrap } from "@duplojs/utils"; +import { createTransformer } from "../create"; +import { factory, SyntaxKind } from "typescript"; + +export const lazyTransformer = createTransformer( + DP.lazyKind.has, + ( + dataParser, + { + success, + dependencyIdentifier, + getDefinition, + transformer, + }, + ) => { + const getter = transformer(dataParser.definition.getter.value); + + if (E.isLeft(getter)) { + return getter; + } + + const definition = getDefinition(); + + if (E.isLeft(definition)) { + return definition; + } + + return pipe( + factory.createCallExpression( + factory.createPropertyAccessExpression( + dependencyIdentifier, + factory.createIdentifier("lazy"), + ), + undefined, + [ + factory.createArrowFunction( + undefined, + undefined, + [], + undefined, + factory.createToken(SyntaxKind.EqualsGreaterThanToken), + unwrap(getter), + ), + ...definition, + ], + ), + success, + ); + }, +); diff --git a/scripts/toDataParser/dataParserTransformer/defaults/literal.ts b/scripts/toDataParser/dataParserTransformer/defaults/literal.ts new file mode 100644 index 0000000..8c40e81 --- /dev/null +++ b/scripts/toDataParser/dataParserTransformer/defaults/literal.ts @@ -0,0 +1,71 @@ +import { A, DP, E, isType, P, pipe, pipeCall } from "@duplojs/utils"; +import { createTransformer } from "../create"; +import { factory } from "typescript"; + +export const literalTransformer = createTransformer( + DP.literalKind.has, + ( + dataParser, + { + success, + dependencyIdentifier, + getDefinition, + }, + ) => { + const definition = getDefinition(); + + if (E.isLeft(definition)) { + return definition; + } + + const values = A.map( + dataParser.definition.value, + (element) => P.match(element) + .when( + isType("string"), + factory.createStringLiteral, + ) + .when( + isType("bigint"), + (value) => factory.createBigIntLiteral(`${value.toString()}n`), + ) + .when( + isType("number"), + pipeCall(factory.createNumericLiteral), + ) + .when( + isType("boolean"), + (value) => value + ? factory.createTrue() + : factory.createFalse(), + ) + .when( + isType("null"), + () => factory.createNull(), + ) + .when( + isType("undefined"), + () => factory.createIdentifier("undefined"), + ) + .exhaustive(), + ); + + return pipe( + factory.createCallExpression( + factory.createPropertyAccessExpression( + dependencyIdentifier, + factory.createIdentifier("literal"), + ), + undefined, + [ + factory.createArrayLiteralExpression( + values, + A.minElements(values, 4), + ), + ...definition, + ], + ), + success, + ); + }, +); diff --git a/scripts/toDataParser/dataParserTransformer/defaults/nil.ts b/scripts/toDataParser/dataParserTransformer/defaults/nil.ts new file mode 100644 index 0000000..6a256b8 --- /dev/null +++ b/scripts/toDataParser/dataParserTransformer/defaults/nil.ts @@ -0,0 +1,42 @@ +import { DP, E, pipe } from "@duplojs/utils"; +import { createTransformer } from "../create"; +import { factory } from "typescript"; + +export const nilTransformer = createTransformer( + DP.nilKind.has, + ( + dataParser, + { + success, + dependencyIdentifier, + getDefinition, + }, + ) => { + const definition = getDefinition( + dataParser.definition.coerce + ? [ + factory.createPropertyAssignment( + factory.createIdentifier("coerce"), + factory.createTrue(), + ), + ] + : undefined, + ); + + if (E.isLeft(definition)) { + return definition; + } + + return pipe( + factory.createCallExpression( + factory.createPropertyAccessExpression( + dependencyIdentifier, + factory.createIdentifier("nil"), + ), + undefined, + definition, + ), + success, + ); + }, +); diff --git a/scripts/toDataParser/dataParserTransformer/defaults/nullable.ts b/scripts/toDataParser/dataParserTransformer/defaults/nullable.ts new file mode 100644 index 0000000..f803578 --- /dev/null +++ b/scripts/toDataParser/dataParserTransformer/defaults/nullable.ts @@ -0,0 +1,43 @@ +import { DP, E, pipe, unwrap } from "@duplojs/utils"; +import { createTransformer } from "../create"; +import { factory } from "typescript"; + +export const nullableTransformer = createTransformer( + DP.nullableKind.has, + ( + dataParser, + { + success, + dependencyIdentifier, + getDefinition, + transformer, + }, + ) => { + const inner = transformer(dataParser.definition.inner); + + if (E.isLeft(inner)) { + return inner; + } + + const definition = getDefinition(); + + if (E.isLeft(definition)) { + return definition; + } + + return pipe( + factory.createCallExpression( + factory.createPropertyAccessExpression( + dependencyIdentifier, + factory.createIdentifier("nullable"), + ), + undefined, + [ + unwrap(inner), + ...definition, + ], + ), + success, + ); + }, +); diff --git a/scripts/toDataParser/dataParserTransformer/defaults/number.ts b/scripts/toDataParser/dataParserTransformer/defaults/number.ts new file mode 100644 index 0000000..6a9aa1d --- /dev/null +++ b/scripts/toDataParser/dataParserTransformer/defaults/number.ts @@ -0,0 +1,42 @@ +import { DP, E, pipe } from "@duplojs/utils"; +import { createTransformer } from "../create"; +import { factory } from "typescript"; + +export const numberTransformer = createTransformer( + DP.numberKind.has, + ( + dataParser, + { + success, + dependencyIdentifier, + getDefinition, + }, + ) => { + const definition = getDefinition( + dataParser.definition.coerce + ? [ + factory.createPropertyAssignment( + factory.createIdentifier("coerce"), + factory.createTrue(), + ), + ] + : undefined, + ); + + if (E.isLeft(definition)) { + return definition; + } + + return pipe( + factory.createCallExpression( + factory.createPropertyAccessExpression( + dependencyIdentifier, + factory.createIdentifier("number"), + ), + undefined, + definition, + ), + success, + ); + }, +); diff --git a/scripts/toDataParser/dataParserTransformer/defaults/object.ts b/scripts/toDataParser/dataParserTransformer/defaults/object.ts new file mode 100644 index 0000000..4642884 --- /dev/null +++ b/scripts/toDataParser/dataParserTransformer/defaults/object.ts @@ -0,0 +1,69 @@ +import { A, DP, E, O, pipe, when } from "@duplojs/utils"; +import { createTransformer } from "../create"; +import { factory, type PropertyAssignment } from "typescript"; + +export const objectTransformer = createTransformer( + DP.objectKind.has, + ( + dataParser, + { + success, + dependencyIdentifier, + getDefinition, + transformer, + }, + ) => { + const shape = pipe( + dataParser.definition.shape, + O.entries, + A.reduce( + A.reduceFrom([]), + ({ element: [identifier, parser], lastValue, nextPush, exit }) => pipe( + parser, + transformer, + when( + E.isLeft, + exit, + ), + E.whenIsRight( + (result) => nextPush( + lastValue, + factory.createPropertyAssignment( + factory.createIdentifier(identifier), + result, + ), + ), + ), + ), + ), + ); + + if (E.isLeft(shape)) { + return shape; + } + + const definition = getDefinition(); + + if (E.isLeft(definition)) { + return definition; + } + + return pipe( + factory.createCallExpression( + factory.createPropertyAccessExpression( + dependencyIdentifier, + factory.createIdentifier("object"), + ), + undefined, + [ + factory.createObjectLiteralExpression( + shape, + true, + ), + ...definition, + ], + ), + success, + ); + }, +); diff --git a/scripts/toDataParser/dataParserTransformer/defaults/optional.ts b/scripts/toDataParser/dataParserTransformer/defaults/optional.ts new file mode 100644 index 0000000..1798e47 --- /dev/null +++ b/scripts/toDataParser/dataParserTransformer/defaults/optional.ts @@ -0,0 +1,43 @@ +import { DP, E, pipe, unwrap } from "@duplojs/utils"; +import { createTransformer } from "../create"; +import { factory } from "typescript"; + +export const optionalTransformer = createTransformer( + DP.optionalKind.has, + ( + dataParser, + { + success, + dependencyIdentifier, + getDefinition, + transformer, + }, + ) => { + const inner = transformer(dataParser.definition.inner); + + if (E.isLeft(inner)) { + return inner; + } + + const definition = getDefinition(); + + if (E.isLeft(definition)) { + return definition; + } + + return pipe( + factory.createCallExpression( + factory.createPropertyAccessExpression( + dependencyIdentifier, + factory.createIdentifier("optional"), + ), + undefined, + [ + unwrap(inner), + ...definition, + ], + ), + success, + ); + }, +); diff --git a/scripts/toDataParser/dataParserTransformer/defaults/pipe.ts b/scripts/toDataParser/dataParserTransformer/defaults/pipe.ts new file mode 100644 index 0000000..0ca443f --- /dev/null +++ b/scripts/toDataParser/dataParserTransformer/defaults/pipe.ts @@ -0,0 +1,50 @@ +import { DP, E, pipe, unwrap } from "@duplojs/utils"; +import { createTransformer } from "../create"; +import { factory } from "typescript"; + +export const pipeTransformer = createTransformer( + DP.pipeKind.has, + ( + dataParser, + { + success, + dependencyIdentifier, + getDefinition, + transformer, + }, + ) => { + const input = transformer(dataParser.definition.input); + + if (E.isLeft(input)) { + return input; + } + + const output = transformer(dataParser.definition.output); + + if (E.isLeft(output)) { + return output; + } + + const definition = getDefinition(); + + if (E.isLeft(definition)) { + return definition; + } + + return pipe( + factory.createCallExpression( + factory.createPropertyAccessExpression( + dependencyIdentifier, + factory.createIdentifier("pipe"), + ), + undefined, + [ + unwrap(input), + unwrap(output), + ...definition, + ], + ), + success, + ); + }, +); diff --git a/scripts/toDataParser/dataParserTransformer/defaults/record.ts b/scripts/toDataParser/dataParserTransformer/defaults/record.ts new file mode 100644 index 0000000..31929c1 --- /dev/null +++ b/scripts/toDataParser/dataParserTransformer/defaults/record.ts @@ -0,0 +1,50 @@ +import { DP, E, pipe, unwrap } from "@duplojs/utils"; +import { createTransformer } from "../create"; +import { factory } from "typescript"; + +export const recordTransformer = createTransformer( + DP.recordKind.has, + ( + dataParser, + { + success, + dependencyIdentifier, + getDefinition, + transformer, + }, + ) => { + const key = transformer(dataParser.definition.key); + + if (E.isLeft(key)) { + return key; + } + + const value = transformer(dataParser.definition.value); + + if (E.isLeft(value)) { + return value; + } + + const definition = getDefinition(); + + if (E.isLeft(definition)) { + return definition; + } + + return pipe( + factory.createCallExpression( + factory.createPropertyAccessExpression( + dependencyIdentifier, + factory.createIdentifier("record"), + ), + undefined, + [ + unwrap(key), + unwrap(value), + ...definition, + ], + ), + success, + ); + }, +); diff --git a/scripts/toDataParser/dataParserTransformer/defaults/recover.ts b/scripts/toDataParser/dataParserTransformer/defaults/recover.ts new file mode 100644 index 0000000..b787a27 --- /dev/null +++ b/scripts/toDataParser/dataParserTransformer/defaults/recover.ts @@ -0,0 +1,12 @@ +import { DP } from "@duplojs/utils"; +import { createTransformer } from "../create"; + +export const recoverTransformer = createTransformer( + DP.recoverKind.has, + ( + dataParser, + { + transformer, + }, + ) => transformer(dataParser.definition.inner), +); diff --git a/scripts/toDataParser/dataParserTransformer/defaults/string.ts b/scripts/toDataParser/dataParserTransformer/defaults/string.ts new file mode 100644 index 0000000..9b17658 --- /dev/null +++ b/scripts/toDataParser/dataParserTransformer/defaults/string.ts @@ -0,0 +1,33 @@ +import { DP, E, pipe } from "@duplojs/utils"; +import { createTransformer } from "../create"; +import { factory } from "typescript"; + +export const stringTransformer = createTransformer( + DP.stringKind.has, + ( + dataParser, + { + success, + dependencyIdentifier, + getDefinition, + }, + ) => { + const definition = getDefinition(); + + if (E.isLeft(definition)) { + return definition; + } + + return pipe( + factory.createCallExpression( + factory.createPropertyAccessExpression( + dependencyIdentifier, + factory.createIdentifier("string"), + ), + undefined, + definition, + ), + success, + ); + }, +); diff --git a/scripts/toDataParser/dataParserTransformer/defaults/templateLiteral.ts b/scripts/toDataParser/dataParserTransformer/defaults/templateLiteral.ts new file mode 100644 index 0000000..ce1f62b --- /dev/null +++ b/scripts/toDataParser/dataParserTransformer/defaults/templateLiteral.ts @@ -0,0 +1,91 @@ +import { A, DP, E, isType, P, pipe, pipeCall, unwrap } from "@duplojs/utils"; +import { createTransformer } from "../create"; +import { type Expression, factory } from "typescript"; + +export const templateLiteralTransformer = createTransformer( + DP.templateLiteralKind.has, + ( + dataParser, + { + success, + dependencyIdentifier, + getDefinition, + transformer, + }, + ) => { + const parts = A.reduce( + dataParser.definition.template, + A.reduceFrom([]), + ({ element, lastValue, nextPush, exit }) => { + if (DP.dataParserKind.has(element)) { + const transformResult = transformer(element); + + if (E.isLeft(transformResult)) { + return exit(transformResult); + } + + return nextPush(lastValue, unwrap(transformResult)); + } + + const result = P.match(element) + .when( + isType("bigint"), + (value) => factory.createBigIntLiteral(`${value.toString()}n`), + ) + .when( + isType("boolean"), + (value) => value + ? factory.createTrue() + : factory.createFalse(), + ) + .when( + isType("string"), + pipeCall(factory.createStringLiteral), + ) + .when( + isType("number"), + pipeCall(factory.createNumericLiteral), + ) + .when( + isType("null"), + () => factory.createNull(), + ) + .when( + isType("undefined"), + () => factory.createIdentifier("undefined"), + ) + .exhaustive(); + + return nextPush(lastValue, result); + }, + ); + + if (E.isLeft(parts)) { + return parts; + } + + const definition = getDefinition(); + + if (E.isLeft(definition)) { + return definition; + } + + return pipe( + factory.createCallExpression( + factory.createPropertyAccessExpression( + dependencyIdentifier, + factory.createIdentifier("templateLiteral"), + ), + undefined, + [ + factory.createArrayLiteralExpression( + parts, + A.minElements(parts, 4), + ), + ...definition, + ], + ), + success, + ); + }, +); diff --git a/scripts/toDataParser/dataParserTransformer/defaults/time.ts b/scripts/toDataParser/dataParserTransformer/defaults/time.ts new file mode 100644 index 0000000..61e7ff5 --- /dev/null +++ b/scripts/toDataParser/dataParserTransformer/defaults/time.ts @@ -0,0 +1,42 @@ +import { DP, E, pipe } from "@duplojs/utils"; +import { createTransformer } from "../create"; +import { factory } from "typescript"; + +export const timeTransformer = createTransformer( + DP.timeKind.has, + ( + dataParser, + { + success, + dependencyIdentifier, + getDefinition, + }, + ) => { + const definition = getDefinition( + dataParser.definition.coerce + ? [ + factory.createPropertyAssignment( + factory.createIdentifier("coerce"), + factory.createTrue(), + ), + ] + : undefined, + ); + + if (E.isLeft(definition)) { + return definition; + } + + return pipe( + factory.createCallExpression( + factory.createPropertyAccessExpression( + dependencyIdentifier, + factory.createIdentifier("time"), + ), + undefined, + definition, + ), + success, + ); + }, +); diff --git a/scripts/toDataParser/dataParserTransformer/defaults/transform.ts b/scripts/toDataParser/dataParserTransformer/defaults/transform.ts new file mode 100644 index 0000000..0397686 --- /dev/null +++ b/scripts/toDataParser/dataParserTransformer/defaults/transform.ts @@ -0,0 +1,77 @@ +import { DP, E, pipe, unwrap } from "@duplojs/utils"; +import { createTransformer } from "../create"; +import { createSourceFile, factory, isArrowFunction, isExpressionStatement, isFunctionExpression, isParenthesizedExpression, ScriptTarget } from "typescript"; + +export const transformTransformer = createTransformer( + DP.transformKind.has, + ( + dataParser, + { + success, + dependencyIdentifier, + getDefinition, + transformer, + buildError, + }, + ) => { + const inner = transformer(dataParser.definition.inner); + + if (E.isLeft(inner)) { + return inner; + } + + const theFunction = dataParser.definition.theFunction.toString(); + + if (theFunction.includes("[native code]")) { + return buildError(); + } + + const sourceFile = createSourceFile( + "refine-function.ts", + `(${theFunction})`, + ScriptTarget.Latest, + false, + ); + + const statement = sourceFile.statements[0]; + if (!statement || !isExpressionStatement(statement)) { + return buildError(); + } + + const functionExpression = isParenthesizedExpression(statement.expression) + ? statement.expression.expression + : undefined; + + if ( + !functionExpression + || ( + !isFunctionExpression(functionExpression) + && !isArrowFunction(functionExpression) + ) + ) { + return buildError(); + } + + const definition = getDefinition(); + + if (E.isLeft(definition)) { + return definition; + } + + return pipe( + factory.createCallExpression( + factory.createPropertyAccessExpression( + dependencyIdentifier, + factory.createIdentifier("transform"), + ), + undefined, + [ + unwrap(inner), + functionExpression, + ...definition, + ], + ), + success, + ); + }, +); diff --git a/scripts/toDataParser/dataParserTransformer/defaults/tuple.ts b/scripts/toDataParser/dataParserTransformer/defaults/tuple.ts new file mode 100644 index 0000000..83c6a76 --- /dev/null +++ b/scripts/toDataParser/dataParserTransformer/defaults/tuple.ts @@ -0,0 +1,77 @@ +import { A, DP, E, pipe, unwrap } from "@duplojs/utils"; +import { createTransformer } from "../create"; +import { type Expression, factory } from "typescript"; + +export const tupleTransformer = createTransformer( + DP.tupleKind.has, + ( + dataParser, + { + success, + dependencyIdentifier, + getDefinition, + transformer, + }, + ) => { + const shape = A.reduce( + dataParser.definition.shape, + A.reduceFrom([]), + ({ element, lastValue, nextPush, exit }) => { + const result = transformer(element); + + if (E.isLeft(result)) { + return exit(result); + } + + return nextPush(lastValue, unwrap(result)); + }, + ); + + if (E.isLeft(shape)) { + return shape; + } + + const rest = dataParser.definition.rest + ? pipe( + dataParser.definition.rest, + transformer, + E.whenIsRight( + (expression) => [ + factory.createPropertyAssignment( + factory.createIdentifier("rest"), + expression, + ), + ], + ), + ) + : undefined; + + if (E.isLeft(rest)) { + return rest; + } + + const definition = getDefinition(rest); + + if (E.isLeft(definition)) { + return definition; + } + + return pipe( + factory.createCallExpression( + factory.createPropertyAccessExpression( + dependencyIdentifier, + factory.createIdentifier("tuple"), + ), + undefined, + [ + factory.createArrayLiteralExpression( + shape, + A.minElements(shape, 3), + ), + ...definition, + ], + ), + success, + ); + }, +); diff --git a/scripts/toDataParser/dataParserTransformer/defaults/union.ts b/scripts/toDataParser/dataParserTransformer/defaults/union.ts new file mode 100644 index 0000000..a5e8013 --- /dev/null +++ b/scripts/toDataParser/dataParserTransformer/defaults/union.ts @@ -0,0 +1,58 @@ +import { A, DP, E, pipe, unwrap } from "@duplojs/utils"; +import { createTransformer } from "../create"; +import { type Expression, factory } from "typescript"; + +export const unionTransformer = createTransformer( + DP.unionKind.has, + ( + dataParser, + { + dependencyIdentifier, + getDefinition, + success, + transformer, + }, + ) => { + const options = A.reduce( + dataParser.definition.options, + A.reduceFrom([]), + ({ element, lastValue, nextPush, exit }) => { + const result = transformer(element); + + if (E.isLeft(result)) { + return exit(result); + } + + return nextPush(lastValue, unwrap(result)); + }, + ); + + if (E.isLeft(options)) { + return options; + } + + const definition = getDefinition(); + + if (E.isLeft(definition)) { + return definition; + } + + return pipe( + factory.createCallExpression( + factory.createPropertyAccessExpression( + dependencyIdentifier, + factory.createIdentifier("union"), + ), + undefined, + [ + factory.createArrayLiteralExpression( + options, + true, + ), + ...definition, + ], + ), + success, + ); + }, +); diff --git a/scripts/toDataParser/dataParserTransformer/defaults/unknown.ts b/scripts/toDataParser/dataParserTransformer/defaults/unknown.ts new file mode 100644 index 0000000..ec89969 --- /dev/null +++ b/scripts/toDataParser/dataParserTransformer/defaults/unknown.ts @@ -0,0 +1,33 @@ +import { DP, E, pipe } from "@duplojs/utils"; +import { createTransformer } from "../create"; +import { factory } from "typescript"; + +export const unknownTransformer = createTransformer( + DP.unknownKind.has, + ( + dataParser, + { + success, + dependencyIdentifier, + getDefinition, + }, + ) => { + const definition = getDefinition(); + + if (E.isLeft(definition)) { + return definition; + } + + return pipe( + factory.createCallExpression( + factory.createPropertyAccessExpression( + dependencyIdentifier, + factory.createIdentifier("unknown"), + ), + undefined, + definition, + ), + success, + ); + }, +); diff --git a/scripts/toDataParser/dataParserTransformer/getDefinitionDataParser.ts b/scripts/toDataParser/dataParserTransformer/getDefinitionDataParser.ts new file mode 100644 index 0000000..11ec1dd --- /dev/null +++ b/scripts/toDataParser/dataParserTransformer/getDefinitionDataParser.ts @@ -0,0 +1,75 @@ +import { A, type DP, E, pipe, when } from "@duplojs/utils"; +import { checkerTransformer, type createCheckerTransformer } from "../checkerTransformer"; +import { type CallExpression, factory, type PropertyAssignment } from "typescript"; +import type * as TST from "@scripts/toTypescript"; + +export interface getDefinitionDataParserParams { + readonly dataParser: DP.DataParser; + readonly checkerTransformers: readonly ReturnType[]; + readonly importContext: TST.MapImportContext; + readonly customProperties: readonly PropertyAssignment[]; +} + +export function getDefinitionDataParser(params: getDefinitionDataParserParams) { + const propertyAssignments: PropertyAssignment[] = []; + + if (params.dataParser.definition.errorMessage) { + propertyAssignments.push( + factory.createPropertyAssignment( + factory.createIdentifier("errorMessage"), + factory.createStringLiteral(params.dataParser.definition.errorMessage), + ), + ); + } + if (A.minElements(params.customProperties, 1)) { + propertyAssignments.push(...params.customProperties); + } + if (A.minElements(params.dataParser.definition.checkers, 1)) { + const checkers = A.reduce( + params.dataParser.definition.checkers, + A.reduceFrom([]), + ({ element, lastValue, nextPush, exit }) => pipe( + checkerTransformer( + element, + { + transformers: params.checkerTransformers, + importContext: params.importContext, + }, + ), + E.whenIsRight( + (value) => nextPush(lastValue, value), + ), + when( + E.isLeft, + exit, + ), + ), + ); + + if (E.isLeft(checkers)) { + return E.left("buildDataParserGetDefinitionError", { + dataParser: params.dataParser, + error: checkers, + }); + } + + propertyAssignments.push( + factory.createPropertyAssignment( + factory.createIdentifier("checkers"), + factory.createArrayLiteralExpression( + checkers, + A.minElements(checkers, 2), + ), + ), + ); + } + + return A.minElements(propertyAssignments, 1) + ? [ + factory.createObjectLiteralExpression( + propertyAssignments, + A.minElements(propertyAssignments, 2), + ), + ] + : []; +} diff --git a/scripts/toDataParser/dataParserTransformer/hook.ts b/scripts/toDataParser/dataParserTransformer/hook.ts new file mode 100644 index 0000000..7ff329c --- /dev/null +++ b/scripts/toDataParser/dataParserTransformer/hook.ts @@ -0,0 +1,23 @@ +import type { DP } from "@duplojs/utils"; +import type * as TST from "@scripts/toTypescript"; +import type { MapContext } from "./create"; + +export type TransformerHookAction = "stop" | "next"; + +export interface TransformerHookOutput { + dataParser: DP.DataParsers; + action: TransformerHookAction; +} + +export interface TransformerHookParams { + dataParser: DP.DataParsers; + context: MapContext; + importContext: TST.MapImportContext; + + output( + action: TransformerHookAction, + dataParser: DP.DataParsers + ): TransformerHookOutput; +} + +export type TransformerHook = (params: TransformerHookParams) => TransformerHookOutput; diff --git a/scripts/toDataParser/dataParserTransformer/index.ts b/scripts/toDataParser/dataParserTransformer/index.ts new file mode 100644 index 0000000..92cd93d --- /dev/null +++ b/scripts/toDataParser/dataParserTransformer/index.ts @@ -0,0 +1,6 @@ +export * from "./create"; +export * from "./transformer"; +export * from "./hook"; +export * from "./defaults"; +export * from "./getDefinitionDataParser"; +export * from "./createIdentifier"; diff --git a/scripts/toDataParser/dataParserTransformer/transformer.ts b/scripts/toDataParser/dataParserTransformer/transformer.ts new file mode 100644 index 0000000..4f645a1 --- /dev/null +++ b/scripts/toDataParser/dataParserTransformer/transformer.ts @@ -0,0 +1,217 @@ +import { A, E, type DP, unwrap, justExec } from "@duplojs/utils"; +import * as TST from "@scripts/toTypescript"; +import type { MapContext, TransformerParams, createTransformer, MaybeTransformerEither, DependenciesContext, MapContextValue } from "./create"; +import { factory, type Identifier } from "typescript"; +import type { TransformerHook } from "./hook"; +import type { createCheckerTransformer } from "../checkerTransformer"; +import { getDefinitionDataParser } from "./getDefinitionDataParser"; +import { createIdentifier } from "./createIdentifier"; + +export interface TransformerFunctionParams { + readonly dataParserTransformers: readonly ReturnType[]; + readonly checkerTransformers: readonly ReturnType[]; + readonly typescriptTransformers: readonly ReturnType[]; + readonly context: MapContext; + readonly typescriptContext: TST.MapContext; + readonly importContext: TST.MapImportContext; + readonly dependenciesContext: DependenciesContext; + readonly dependencyIdentifier: Identifier; + readonly hooks: readonly TransformerHook[]; + readonly recursiveDataParsers: DP.DataParser[]; + readonly toTypescript?: { + readonly mode?: TST.TransformerMode; + readonly hooks?: readonly TST.TransformerHook[]; + }; +} + +export function transformer( + dataParser: DP.DataParser, + params: TransformerFunctionParams, +) { + const currentDataParser = A.reduce( + params.hooks, + A.reduceFrom(dataParser), + ({ element: hook, lastValue, next, exit }) => { + const result = hook({ + dataParser: lastValue, + context: params.context, + importContext: params.importContext, + output: (action, dataParser) => ({ + dataParser, + action, + }), + }); + if (result.action === "stop") { + return exit(result.dataParser); + } else { + return next(result.dataParser); + } + }, + ); + + if (currentDataParser.definition.identifier) { + params.dependenciesContext.add(currentDataParser); + } + + const identifiedDataParser = params.context.get(currentDataParser); + + if (identifiedDataParser) { + return E.right( + "buildSuccess", + identifiedDataParser.identifier, + ); + } + + const newIdentifiedDataParser = justExec(() => { + const currentIdentifier = A.includes(params.recursiveDataParsers, currentDataParser) + || !!currentDataParser.definition.identifier + ? factory.createIdentifier( + currentDataParser.definition.identifier !== undefined + ? createIdentifier(currentDataParser.definition.identifier) + : `recursive${params.context.size}DataParser`, + ) + : undefined; + + if (!currentIdentifier) { + return null; + } + + const contextValue: MapContextValue = { + identifier: currentIdentifier, + expression: factory.createIdentifier("undefined"), + typeIdentifier: null, + dependencies: new Set(), + }; + + params.context.set( + currentDataParser, + contextValue, + ); + + return contextValue; + }); + + const functionParams: TransformerParams = { + success(result) { + return E.right("buildSuccess", result); + }, + transformer(dataParser) { + return transformer( + dataParser, + { + ...params, + dependenciesContext: newIdentifiedDataParser?.dependencies + ?? params.dependenciesContext, + }, + ); + }, + context: params.context, + dependencyIdentifier: params.dependencyIdentifier, + buildError() { + return E.left("buildDataParserError", currentDataParser); + }, + importContext: params.importContext, + getDefinition(customProperties = []) { + return getDefinitionDataParser({ + dataParser: currentDataParser, + checkerTransformers: params.checkerTransformers, + importContext: params.importContext, + customProperties, + }); + }, + addImport: TST.createAddImport(params.importContext), + }; + + const result = currentDataParser.definition.overrideDataParserTransformer + ? currentDataParser.definition.overrideDataParserTransformer( + currentDataParser.addOverrideDataParserTransformer(null), + functionParams, + ) + : A.reduce( + params.dataParserTransformers, + A.reduceFrom( + E.left("dataParserNotSupport", currentDataParser), + ), + ({ + element: functionBuilder, + lastValue, + next, + exit, + }) => { + const result = functionBuilder(currentDataParser, functionParams); + + if (E.isLeft(result)) { + if (!E.hasInformation(result, "dataParserNotSupport")) { + return exit(result); + } + + return next(lastValue); + } + + return exit(result); + }, + ); + + if (E.isLeft(result)) { + return result; + } + + if (newIdentifiedDataParser) { + const typeIdentifier = justExec(() => { + if (!A.includes(params.recursiveDataParsers, currentDataParser)) { + return null; + } + const identifier = `$${newIdentifiedDataParser.identifier.text}`; + + const result = TST.buildContext( + currentDataParser, + { + identifier, + transformers: params.typescriptTransformers, + context: params.typescriptContext, + importContext: params.importContext, + ...params.toTypescript, + }, + ); + return E.matchInformationOtherwise( + result, + { + buildDataParserError: (dataParser) => E.left( + "toTypescriptBuildDataParserError", + dataParser, + ), + dataParserNotSupport: (dataParser) => E.left( + "toTypescriptDataParserNotSupport", + dataParser, + ), + }, + () => factory.createIdentifier(identifier), + ); + }); + + if (E.isLeft(typeIdentifier)) { + return typeIdentifier; + } + + params.context.delete(currentDataParser); + + params.context.set( + currentDataParser, + { + ...newIdentifiedDataParser, + expression: unwrap(result), + typeIdentifier: typeIdentifier, + }, + ); + + return E.right( + "buildSuccess", + newIdentifiedDataParser.identifier, + ); + } + + return E.right( + "buildSuccess", + unwrap(result), + ); +} diff --git a/scripts/toDataParser/index.ts b/scripts/toDataParser/index.ts new file mode 100644 index 0000000..c7996cf --- /dev/null +++ b/scripts/toDataParser/index.ts @@ -0,0 +1,7 @@ +export * from "./kind"; +export * from "./override"; +export * from "./checkerTransformer"; +export * from "./dataParserTransformer"; +export * from "./render"; +export * from "./buildContext"; +export * from "./printer"; diff --git a/scripts/toDataParser/kind.ts b/scripts/toDataParser/kind.ts new file mode 100644 index 0000000..0487403 --- /dev/null +++ b/scripts/toDataParser/kind.ts @@ -0,0 +1,12 @@ +import { createKindNamespace } from "@duplojs/utils"; + +declare module "@duplojs/utils" { + interface ReservedKindNamespace { + DuplojsDataParserToolsToDataParser: true; + } +} + +export const createToDataParserKind = createKindNamespace( + // @ts-expect-error reserved kind namespace + "DuplojsDataParserToolsToDataParser", +); diff --git a/scripts/toDataParser/override.ts b/scripts/toDataParser/override.ts new file mode 100644 index 0000000..5cb2613 --- /dev/null +++ b/scripts/toDataParser/override.ts @@ -0,0 +1,75 @@ +import { dataParserBaseInit } from "@duplojs/utils/dataParser"; +import type { CallExpression, Identifier } from "typescript"; +import type { TransformerBuildFunction } from "./dataParserTransformer"; + +declare module "@duplojs/utils/dataParser" { + interface DataParserBase { + + /** + * @deprecated this method mutated the dataParser by adding an identifier + */ + setIdentifier(input: string): this; + addIdentifier(input: string): this; + + /** + * @deprecated this method mutated the dataParser by adding an override transformer + */ + setOverrideDataParserTransformer( + transformer: CallExpression | Identifier | TransformerBuildFunction | null, + ): this; + addOverrideDataParserTransformer( + transformer: CallExpression | Identifier | TransformerBuildFunction | null, + ): this; + } + + interface DataParserDefinition { + identifier?: string; + overrideDataParserTransformer?: TransformerBuildFunction; + } +} + +dataParserBaseInit.overrideHandler.setMethod( + "setIdentifier", + (schema, identifier) => { + schema.definition.identifier = identifier; + + return schema; + }, +); + +dataParserBaseInit.overrideHandler.setMethod( + "addIdentifier", + (schema, identifier) => { + const newSchema = schema.clone(); + + newSchema.setIdentifier(identifier); + + return newSchema; + }, +); + +dataParserBaseInit.overrideHandler.setMethod( + "setOverrideDataParserTransformer", + (schema, overrideTransformer) => { + if (overrideTransformer) { + schema.definition.overrideDataParserTransformer = typeof overrideTransformer === "function" + ? overrideTransformer + : (__, { success }) => success(overrideTransformer); + } else { + schema.definition.overrideDataParserTransformer = undefined; + } + + return schema; + }, +); + +dataParserBaseInit.overrideHandler.setMethod( + "addOverrideDataParserTransformer", + (schema, overrideTransformer) => { + const newSchema = schema.clone(); + + newSchema.setOverrideDataParserTransformer(overrideTransformer); + + return newSchema; + }, +); diff --git a/scripts/toDataParser/printer.ts b/scripts/toDataParser/printer.ts new file mode 100644 index 0000000..22f492e --- /dev/null +++ b/scripts/toDataParser/printer.ts @@ -0,0 +1,56 @@ +import { createPrinter, createSourceFile, EmitHint, factory, NodeFlags, ScriptKind, ScriptTarget, SyntaxKind, type VariableStatement } from "typescript"; +import { type BuildedContext } from "./buildContext"; +import { A, G, pipe, S } from "@duplojs/utils"; +import * as TST from "@scripts/toTypescript"; + +export function printer(params: BuildedContext) { + const sourceFile = createSourceFile("print.ts", "", ScriptTarget.Latest, false, ScriptKind.TS); + const printer = createPrinter(); + + const dataParserStatements = G.reduce( + params.context.values(), + G.reduceFrom([]), + ({ element: contextValue, lastValue, nextPush }) => nextPush( + lastValue, + factory.createVariableStatement( + [factory.createToken(SyntaxKind.ExportKeyword)], + factory.createVariableDeclarationList( + [ + factory.createVariableDeclaration( + contextValue.identifier, + undefined, + contextValue.typeIdentifier + ? factory.createTypeReferenceNode( + factory.createQualifiedName( + factory.createIdentifier(params.importMode === "extended" ? "DPE" : "DP"), + factory.createIdentifier(params.importMode === "extended" ? "DataParserExtended" : "DataParser"), + ), + [factory.createTypeReferenceNode(contextValue.typeIdentifier)], + ) + : undefined, + contextValue.expression, + ), + ], + NodeFlags.Const, + ), + ), + ), + ); + + return pipe( + [ + ...TST.createImportDeclaration(params.importContext), + ...params.typescriptContext.values(), + ...dataParserStatements, + ], + A.map( + (value) => printer.printNode( + EmitHint.Unspecified, + value, + sourceFile, + ), + ), + A.join("\n\n"), + S.trim, + ); +} diff --git a/scripts/toDataParser/render.ts b/scripts/toDataParser/render.ts new file mode 100644 index 0000000..7fb69bc --- /dev/null +++ b/scripts/toDataParser/render.ts @@ -0,0 +1,70 @@ +import type * as TST from "@scripts/toTypescript"; +import { type DataParserNotSupportedEither, type DataParserGetDefinitionErrorEither, type DataParserErrorEither, type MapContext, type ToTypescriptDataParserErrorEither, type ToTypescriptDataParserNotSupportedEither } from "./dataParserTransformer"; +import { createToDataParserKind } from "./kind"; +import { buildContext, type BuildContextParams } from "./buildContext"; +import { type DP, E, kindClass, unwrap } from "@duplojs/utils"; +import { printer } from "./printer"; + +export class DataParserToDataParserRenderError extends kindClass( + createToDataParserKind("data-parser-to-data-parser-render-error"), + Error, +) { + public constructor( + public dataParser: DP.DataParser, + public error: DataParserNotSupportedEither | DataParserErrorEither | DataParserGetDefinitionErrorEither, + ) { + super({}, "Error during the render of dataParser in dataParser."); + } +} + +export class DataParserToDataParserTypeRenderError extends kindClass( + createToDataParserKind("data-parser-to-data-parser-type-render-error"), + Error, +) { + public constructor( + public dataParser: DP.DataParser, + public error: ToTypescriptDataParserNotSupportedEither | ToTypescriptDataParserErrorEither, + ) { + super({}, "Error during the render of recursive dataParser type."); + } +} + +export interface RenderParams extends BuildContextParams { + +} + +export function render(dataParser: DP.DataParser, params: RenderParams) { + const context: MapContext = new Map(params.context); + const typescriptContext: TST.MapContext = new Map(params.typescriptContext); + const importContext: TST.MapImportContext = new Map(params.importContext); + + const result = buildContext(dataParser, { + ...params, + context, + typescriptContext, + importContext, + }); + + if ( + E.hasInformation(result, "buildDataParserError") + || E.hasInformation(result, "dataParserNotSupport") + || E.hasInformation(result, "buildDataParserGetDefinitionError") + ) { + throw new DataParserToDataParserRenderError( + dataParser, + result, + ); + } else if ( + E.hasInformation(result, "toTypescriptBuildDataParserError") + || E.hasInformation(result, "toTypescriptDataParserNotSupport") + ) { + throw new DataParserToDataParserTypeRenderError( + dataParser, + result, + ); + } + + return printer( + unwrap(result), + ); +} diff --git a/scripts/toJsonSchema/transformer/defaults/literal.ts b/scripts/toJsonSchema/transformer/defaults/literal.ts index 911ae8b..b56179b 100644 --- a/scripts/toJsonSchema/transformer/defaults/literal.ts +++ b/scripts/toJsonSchema/transformer/defaults/literal.ts @@ -1,5 +1,5 @@ import { A, DP, isType, justReturn, P, pipe } from "@duplojs/utils"; -import { createTransformer, type SupportedVersions, type SupportedVersionsUrl } from "../create"; +import { createTransformer, type SupportedVersions } from "../create"; type JsonPrimitive = string | number | boolean | null; type JsonType = "string" | "number" | "integer" | "boolean" | "null"; diff --git a/scripts/toJsonSchema/transformer/transformer.ts b/scripts/toJsonSchema/transformer/transformer.ts index edf651d..c6ff83b 100644 --- a/scripts/toJsonSchema/transformer/transformer.ts +++ b/scripts/toJsonSchema/transformer/transformer.ts @@ -1,4 +1,4 @@ -import { A, E, justExec, justReturn, unwrap, whenElse, type DP } from "@duplojs/utils"; +import { A, E, justExec, unwrap, type DP } from "@duplojs/utils"; import { type MapContext, type DataParserNotSupportedEither, diff --git a/scripts/toTypescript/buildContext.ts b/scripts/toTypescript/buildContext.ts new file mode 100644 index 0000000..97e17dc --- /dev/null +++ b/scripts/toTypescript/buildContext.ts @@ -0,0 +1,75 @@ +import { DP, E, unwrap } from "@duplojs/utils"; +import { createIdentifier, type createTransformer, transformer, type MapContext, type MapImportContext, type TransformerHook, type TransformerMode, type DataParserErrorEither, type DataParserNotSupportedEither } from "./transformer"; +import { getRecursiveDataParser } from "@scripts/utils"; +import { factory, SyntaxKind } from "typescript"; + +export interface BuildedContext { + readonly context: MapContext; + readonly importContext: MapImportContext; +} + +export interface BuildContextParams { + readonly identifier: string; + readonly transformers: readonly ReturnType[]; + readonly context?: MapContext; + readonly mode?: TransformerMode; + readonly hooks?: readonly TransformerHook[]; + readonly importContext?: MapImportContext; +} + +export function buildContext( + schema: DP.DataParser, + params: BuildContextParams, +): ( + | E.Success + | DataParserNotSupportedEither + | DataParserErrorEither + ) { + const context: MapContext = params.context ?? new Map(); + const importContext: MapImportContext = params.importContext ?? new Map(); + + const result = transformer( + schema, + { + ...params, + context, + importContext, + mode: params.mode ?? "out", + hooks: params.hooks ?? [], + recursiveDataParsers: getRecursiveDataParser(schema), + }, + ); + + if (E.isLeft(result)) { + return result; + } + + if (!schema.definition.identifier) { + context.set( + DP.empty(), + factory.createTypeAliasDeclaration( + [factory.createToken(SyntaxKind.ExportKeyword)], + factory.createIdentifier(createIdentifier(params.identifier)), + undefined, + unwrap(result), + ), + ); + } else if (schema.definition.identifier !== params.identifier) { + context.set( + DP.empty(), + factory.createTypeAliasDeclaration( + [factory.createToken(SyntaxKind.ExportKeyword)], + factory.createIdentifier(createIdentifier(params.identifier)), + undefined, + factory.createTypeReferenceNode( + createIdentifier(schema.definition.identifier), + ), + ), + ); + } + + return E.success({ + context, + importContext, + }); +} diff --git a/scripts/toTypescript/index.ts b/scripts/toTypescript/index.ts index f73f81c..9d369e4 100644 --- a/scripts/toTypescript/index.ts +++ b/scripts/toTypescript/index.ts @@ -2,3 +2,5 @@ export * from "./transformer"; export * from "./override"; export * from "./render"; export * from "./kind"; +export * from "./buildContext"; +export * from "./printer"; diff --git a/scripts/toTypescript/printer.ts b/scripts/toTypescript/printer.ts new file mode 100644 index 0000000..fd1e27c --- /dev/null +++ b/scripts/toTypescript/printer.ts @@ -0,0 +1,25 @@ +import { createPrinter, createSourceFile, EmitHint, ScriptKind, ScriptTarget } from "typescript"; +import { type BuildedContext } from "./buildContext"; +import { A, pipe, S } from "@duplojs/utils"; +import { createImportDeclaration } from "./transformer"; + +export function printer(params: BuildedContext) { + const sourceFile = createSourceFile("print.ts", "", ScriptTarget.Latest, false, ScriptKind.TS); + const printer = createPrinter(); + + return pipe( + [ + ...createImportDeclaration(params.importContext), + ...params.context.values(), + ], + A.map( + (value) => printer.printNode( + EmitHint.Unspecified, + value, + sourceFile, + ), + ), + A.join("\n\n"), + S.trim, + ); +} diff --git a/scripts/toTypescript/render.ts b/scripts/toTypescript/render.ts index 679bd32..1482ca5 100644 --- a/scripts/toTypescript/render.ts +++ b/scripts/toTypescript/render.ts @@ -1,21 +1,14 @@ -import { DP, unwrap, E, pipe, G, A, S, kindHeritage } from "@duplojs/utils"; -import { type DataParserErrorEither, type DataParserNotSupportedEither, transformer, type MapContext, type TransformerMode, type TransformerHook, type createTransformer, type MapImportType } from "./transformer"; -import { createPrinter, createSourceFile, EmitHint, factory, ScriptKind, ScriptTarget, SyntaxKind } from "typescript"; +import { type DP, E, kindClass, unwrap } from "@duplojs/utils"; +import { type DataParserErrorEither, type DataParserNotSupportedEither } from "./transformer"; import { createToTypescriptKind } from "./kind"; -import { getRecursiveDataParser } from "@scripts/utils"; -import { importTypesTransformer } from "./transformer/importTypesTransformer"; +import { buildContext, type BuildContextParams } from "./buildContext"; +import { printer } from "./printer"; + +export interface RenderParams extends BuildContextParams { -export interface RenderParams { - readonly identifier: string; - readonly transformers: readonly ReturnType[]; - readonly context?: MapContext; - readonly mode?: TransformerMode; - readonly hooks?: readonly TransformerHook[]; - readonly importType?: MapImportType; } -export class DataParserToTypescriptRenderError extends kindHeritage( - "data-parser-to-typescript-render-error", +export class DataParserToTypescriptRenderError extends kindClass( createToTypescriptKind("data-parser-to-typescript-render-error"), Error, ) { @@ -23,25 +16,19 @@ export class DataParserToTypescriptRenderError extends kindHeritage( public schema: DP.DataParser, public error: DataParserNotSupportedEither | DataParserErrorEither, ) { - super({}, ["Error during the render of dataParser in typescript type."]); + super({}, "Error during the render of dataParser in typescript type."); } } export function render(schema: DP.DataParser, params: RenderParams) { - const context: MapContext = new Map(params.context); - const importType: MapImportType = new Map(params.importType); + const context = new Map(params.context); + const importContext = new Map(params.importContext); - const result = transformer( - schema, - { - ...params, - context, - mode: params.mode ?? "out", - hooks: params.hooks ?? [], - recursiveDataParsers: getRecursiveDataParser(schema), - importType, - }, - ); + const result = buildContext(schema, { + ...params, + context, + importContext, + }); if (E.isLeft(result)) { throw new DataParserToTypescriptRenderError( @@ -50,46 +37,7 @@ export function render(schema: DP.DataParser, params: RenderParams) { ); } - if (schema.definition.identifier && schema.definition.identifier !== params.identifier) { - context.set( - DP.empty(), - factory.createTypeAliasDeclaration( - [factory.createToken(SyntaxKind.ExportKeyword)], - factory.createIdentifier(params.identifier), - undefined, - factory.createTypeReferenceNode( - schema.definition.identifier, - ), - ), - ); - } else if (schema.definition.identifier !== params.identifier) { - context.set( - DP.empty(), - factory.createTypeAliasDeclaration( - [factory.createToken(SyntaxKind.ExportKeyword)], - factory.createIdentifier(params.identifier), - undefined, - unwrap(result), - ), - ); - } - - const sourceFile = createSourceFile("print.ts", "", ScriptTarget.Latest, false, ScriptKind.TS); - const printer = createPrinter(); - - return pipe( - [ - ...importTypesTransformer(importType), - ...context.values(), - ], - A.map( - (value) => printer.printNode( - EmitHint.Unspecified, - value, - sourceFile, - ), - ), - A.join("\n\n"), - S.trim, + return printer( + unwrap(result), ); } diff --git a/scripts/toTypescript/transformer/addImport.ts b/scripts/toTypescript/transformer/addImport.ts new file mode 100644 index 0000000..d3434d8 --- /dev/null +++ b/scripts/toTypescript/transformer/addImport.ts @@ -0,0 +1,26 @@ +import { A } from "@duplojs/utils"; +import type { MapImportContext } from "./create"; + +export function createAddImport(importContext: MapImportContext) { + return ( + path: string, + typeName: string, + type?: "default" | "namespace" | "direct", + ) => { + const importKind = type === undefined + ? "direct" + : type; + const imports = importContext.get(path) ?? {}; + const identifiers = imports[importKind] ?? []; + + if (!A.includes(identifiers, typeName)) { + importContext.set( + path, + { + ...imports, + [importKind]: A.push(identifiers, typeName), + }, + ); + } + }; +} diff --git a/scripts/toTypescript/transformer/create.ts b/scripts/toTypescript/transformer/create.ts index 0ae8a24..7a1832d 100644 --- a/scripts/toTypescript/transformer/create.ts +++ b/scripts/toTypescript/transformer/create.ts @@ -12,7 +12,14 @@ export type DataParserErrorEither< export type MapContext = Map; -export type MapImportType = Map; +export type MapImportContext = Map< + string, + { + namespace?: string[]; + default?: string[]; + direct?: string[]; + } +>; export type MaybeTransformerEither = | TransformerSuccessEither @@ -24,7 +31,7 @@ export type TransformerMode = "in" | "out"; export interface TransformerParams { readonly mode: TransformerMode; readonly context: MapContext; - readonly importType: MapImportType; + readonly importContext: MapImportContext; transformer( schema: DP.DataParser, @@ -35,7 +42,7 @@ export interface TransformerParams { ): TransformerSuccessEither; buildError(): DataParserErrorEither; - addImport(path: string, typeName: string): void; + addImport(path: string, typeName: string, type?: "default" | "namespace" | "direct"): void; } export type TransformerBuildFunction< diff --git a/scripts/toTypescript/transformer/createIdentifier.ts b/scripts/toTypescript/transformer/createIdentifier.ts new file mode 100644 index 0000000..15fc8d0 --- /dev/null +++ b/scripts/toTypescript/transformer/createIdentifier.ts @@ -0,0 +1,5 @@ +import { S } from "@duplojs/utils"; + +export function createIdentifier(identifier: string) { + return S.capitalize(identifier); +} diff --git a/scripts/toTypescript/transformer/createImportDeclaration.ts b/scripts/toTypescript/transformer/createImportDeclaration.ts new file mode 100644 index 0000000..8c1c008 --- /dev/null +++ b/scripts/toTypescript/transformer/createImportDeclaration.ts @@ -0,0 +1,68 @@ +import { A } from "@duplojs/utils"; +import type { MapImportContext } from "./create"; +import { factory, type ImportDeclaration } from "typescript"; + +export function createImportDeclaration(importContext: MapImportContext) { + return A.reduce( + A.from(importContext), + A.reduceFrom([]), + ({ element: [path, imports], lastValue, next }) => { + const declarations = A.concat( + lastValue, + A.map( + imports.namespace ?? [], + (identifier) => factory.createImportDeclaration( + undefined, + factory.createImportClause( + undefined, + undefined, + factory.createNamespaceImport(factory.createIdentifier(identifier)), + ), + factory.createStringLiteral(path), + undefined, + ), + ), + A.map( + imports.default ?? [], + (identifier) => factory.createImportDeclaration( + undefined, + factory.createImportClause( + undefined, + factory.createIdentifier(identifier), + undefined, + ), + factory.createStringLiteral(path), + undefined, + ), + ), + imports.direct?.length + ? [ + factory.createImportDeclaration( + undefined, + factory.createImportClause( + undefined, + undefined, + factory.createNamedImports( + A.map( + imports.direct, + (identifier) => factory.createImportSpecifier( + false, + undefined, + factory.createIdentifier(identifier), + ), + ), + ), + ), + factory.createStringLiteral(path), + undefined, + ), + ] + : [], + ); + + return next( + declarations, + ); + }, + ); +} diff --git a/scripts/toTypescript/transformer/hook.ts b/scripts/toTypescript/transformer/hook.ts index 4878942..cbb72b6 100644 --- a/scripts/toTypescript/transformer/hook.ts +++ b/scripts/toTypescript/transformer/hook.ts @@ -1,5 +1,5 @@ import type { DP } from "@duplojs/utils"; -import type { MapContext, MapImportType } from "./create"; +import type { MapContext, MapImportContext } from "./create"; export type TransformerHookAction = "stop" | "next"; @@ -11,7 +11,7 @@ export interface TransformerHookOutput { export interface TransformerHookParams { schema: DP.DataParsers; context: MapContext; - importType: MapImportType; + importContext: MapImportContext; output( action: TransformerHookAction, diff --git a/scripts/toTypescript/transformer/importTypesTransformer.ts b/scripts/toTypescript/transformer/importTypesTransformer.ts deleted file mode 100644 index befe51b..0000000 --- a/scripts/toTypescript/transformer/importTypesTransformer.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { pipe, G, A } from "@duplojs/utils"; -import type { MapImportType } from "./create"; -import { factory, SyntaxKind } from "typescript"; - -export function importTypesTransformer(importTypes: MapImportType) { - return pipe( - importTypes, - G.map( - ([path, types]) => factory.createImportDeclaration( - undefined, - factory.createImportClause( - SyntaxKind.TypeKeyword, - undefined, - factory.createNamedImports( - A.map( - types, - (type) => factory.createImportSpecifier( - false, - undefined, - factory.createIdentifier(type), - ), - ), - ), - ), - factory.createStringLiteral(path), - undefined, - ), - ), - ); -} diff --git a/scripts/toTypescript/transformer/index.ts b/scripts/toTypescript/transformer/index.ts index c894811..bd4ab20 100644 --- a/scripts/toTypescript/transformer/index.ts +++ b/scripts/toTypescript/transformer/index.ts @@ -3,4 +3,7 @@ export * from "./transformer"; export * from "./hook"; export * from "./defaults"; export * from "./includesUndefinedTypeNode"; +export * from "./createImportDeclaration"; +export * from "./addImport"; +export * from "./createIdentifier"; export { defaultTransformers } from "./defaults"; diff --git a/scripts/toTypescript/transformer/transformer.ts b/scripts/toTypescript/transformer/transformer.ts index 20f7aff..4c09f73 100644 --- a/scripts/toTypescript/transformer/transformer.ts +++ b/scripts/toTypescript/transformer/transformer.ts @@ -1,7 +1,9 @@ import { A, type DDataParser, E, unwrap, type DP, justExec } from "@duplojs/utils"; -import type { MapContext, DataParserNotSupportedEither, TransformerParams, createTransformer, TransformerMode, DataParserErrorEither, MapImportType } from "./create"; +import type { MapContext, DataParserNotSupportedEither, TransformerParams, createTransformer, TransformerMode, DataParserErrorEither, MapImportContext } from "./create"; import { factory, SyntaxKind } from "typescript"; import type { TransformerHook } from "./hook"; +import { createAddImport } from "./addImport"; +import { createIdentifier } from "./createIdentifier"; export interface TransformerFunctionParams { readonly transformers: readonly ReturnType[]; @@ -9,9 +11,8 @@ export interface TransformerFunctionParams { readonly mode: TransformerMode; readonly hooks: readonly TransformerHook[]; readonly recursiveDataParsers: DDataParser.DataParser[]; - readonly importType: MapImportType; + readonly importContext: MapImportContext; } - export function transformer( schema: DP.DataParser, params: TransformerFunctionParams, @@ -23,7 +24,7 @@ export function transformer( const result = hook({ schema: lastValue, context: params.context, - importType: params.importType, + importContext: params.importContext, output: (action, schema) => ({ schema, action, @@ -56,7 +57,9 @@ export function transformer( return undefined; } - const identifier = currentSchema.definition.identifier ?? `RecursiveType${params.context.size}`; + const identifier = currentSchema.definition.identifier !== undefined + ? createIdentifier(currentSchema.definition.identifier) + : `RecursiveType${params.context.size}`; params.context.set( currentSchema, @@ -88,14 +91,8 @@ export function transformer( buildError() { return E.left("buildDataParserError"); }, - importType: params.importType, - addImport(path, typeName) { - const types = params.importType.get(path) ?? []; - - if (!A.includes(types, typeName)) { - params.importType.set(path, A.push(types, typeName)); - } - }, + importContext: params.importContext, + addImport: createAddImport(params.importContext), }; const result = currentSchema.definition.overrideTypescriptTransformer @@ -153,4 +150,3 @@ export function transformer( return result; } - diff --git a/tests/_utils/setup.ts b/tests/_utils/setup.ts new file mode 100644 index 0000000..e9e4b59 --- /dev/null +++ b/tests/_utils/setup.ts @@ -0,0 +1,21 @@ +import { expect } from "vitest"; + +function areSetsEqual(AA: unknown, BB: unknown): boolean | undefined { + if (!(AA instanceof Set) && !(BB instanceof Set)) { + return undefined; + } + + if (!(AA instanceof Set) || !(BB instanceof Set)) { + return false; + } + + if (AA.size !== BB.size) { + return false; + } + + expect([...AA]).toStrictEqual([...BB]); + + return true; +} + +expect.addEqualityTesters([areSetsEqual]); diff --git a/tests/toDataParser/__snapshots__/dependenciesContext.test.ts.snap b/tests/toDataParser/__snapshots__/dependenciesContext.test.ts.snap new file mode 100644 index 0000000..468c664 --- /dev/null +++ b/tests/toDataParser/__snapshots__/dependenciesContext.test.ts.snap @@ -0,0 +1,45 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`dependencies context > dataParser with no identifier 1`] = ` +"import * as DP from "@duplojs/utils/dataParser"; + +export const test1DataParser = DP.object({ + name: DP.string(), + age: DP.number() +});" +`; + +exports[`dependencies context > dataParser with sub dataParser with identifier 1`] = ` +"import * as DP from "@duplojs/utils/dataParser"; + +export const userNameDataParser = DP.string(); + +export const userAgeDataParser = DP.number(); + +export const otherTestDataParser = DP.object({ + name: userNameDataParser, + age: userAgeDataParser +}); + +export const test4DataParser = otherTestDataParser;" +`; + +exports[`dependencies context > dataParser with top level identifier 1`] = ` +"import * as DP from "@duplojs/utils/dataParser"; + +export const test3DataParser = DP.object({ + name: DP.string(), + age: DP.number() +});" +`; + +exports[`dependencies context > dataParser with top level identifier but it doesn't match with builder identifier 1`] = ` +"import * as DP from "@duplojs/utils/dataParser"; + +export const otherTestDataParser = DP.object({ + name: DP.string(), + age: DP.number() +}); + +export const test2DataParser = otherTestDataParser;" +`; diff --git a/tests/toDataParser/__snapshots__/render.test.ts.snap b/tests/toDataParser/__snapshots__/render.test.ts.snap new file mode 100644 index 0000000..77b386b --- /dev/null +++ b/tests/toDataParser/__snapshots__/render.test.ts.snap @@ -0,0 +1,148 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`render > renders anonymous recursive dataParser with a temporary const 1`] = ` +"import * as DP from "@duplojs/utils/dataParser"; + +export type RecursiveType0 = { + next: RecursiveType0; +}; + +export type $recursive0DataParser = RecursiveType0; + +export const recursive0DataParser: DP.DataParser<$recursive0DataParser> = DP.object({ + next: DP.lazy(() => recursive0DataParser) +}); + +export const recursiveNodeParserDataParser = recursive0DataParser;" +`; + +exports[`render > renders checked dataParsers in normal and extended modes 1`] = ` +"import * as DP from "@duplojs/utils/dataParser"; + +import * as D from "@duplojs/utils/date"; + +export const checkedParserDataParser = DP.object({ + startAt: DP.time({ checkers: [ + DP.checkerTimeMin(D.createTime(1000, "millisecond")), + DP.checkerTimeMax(D.createTime(5000, "millisecond")) + ] }), + name: DP.string({ checkers: [DP.checkerStringMin(2)] }), + count: DP.number({ checkers: [ + DP.checkerInt(), + DP.checkerNumberMin(1) + ] }) +});" +`; + +exports[`render > renders checked dataParsers in normal and extended modes 2`] = ` +"import * as DP from "@duplojs/utils/dataParser"; + +import * as DPE from "@duplojs/utils/dataParserExtended"; + +import * as D from "@duplojs/utils/date"; + +export const checkedExtendedParserDataParser = DPE.object({ + startAt: DPE.time({ checkers: [DP.checkerTimeMin(D.createTime(250, "millisecond"))] }), + tags: DPE.array(DPE.string({ checkers: [DP.checkerStringMin(1)] }), { checkers: [DP.checkerArrayMin(1)] }) +});" +`; + +exports[`render > renders complex nested recursive dataParsers 1`] = ` +"import * as DP from "@duplojs/utils/dataParser"; + +export type RecursiveType0 = (string | RecursiveType0 | RecursiveType1)[]; + +export type RecursiveType1 = { + recursiveProp: { + self: RecursiveType1; + array: RecursiveType0; + tuple: RecursiveType2; + union: RecursiveType3; + }; + children: RecursiveType1[]; + tuple: RecursiveType2; + array: RecursiveType0; + union: RecursiveType3; +}; + +export type RecursiveType2 = [ + string, + RecursiveType1, + (RecursiveType2 | RecursiveType3 | string)[] +]; + +export type RecursiveType3 = string | RecursiveType1 | RecursiveType2 | RecursiveType3[]; + +export type $recursive1DataParser = RecursiveType0; + +export type $recursive3DataParser = RecursiveType3; + +export type $recursive2DataParser = RecursiveType2; + +export type $recursive0DataParser = RecursiveType1; + +export const recursive1DataParser: DP.DataParser<$recursive1DataParser> = DP.array(DP.union([ + DP.string(), + DP.lazy(() => recursive1DataParser), + DP.lazy(() => recursive0DataParser) +])); + +export const recursive3DataParser: DP.DataParser<$recursive3DataParser> = DP.union([ + DP.string(), + DP.lazy(() => recursive0DataParser), + DP.lazy(() => recursive2DataParser), + DP.array(DP.lazy(() => recursive3DataParser)) +]); + +export const recursive2DataParser: DP.DataParser<$recursive2DataParser> = DP.tuple([ + DP.string(), + DP.lazy(() => recursive0DataParser), + DP.array(DP.union([ + DP.lazy(() => recursive2DataParser), + DP.lazy(() => recursive3DataParser), + DP.string() + ])) +]); + +export const recursive0DataParser: DP.DataParser<$recursive0DataParser> = DP.object({ + recursiveProp: DP.object({ + self: DP.lazy(() => recursive0DataParser), + array: recursive1DataParser, + tuple: recursive2DataParser, + union: recursive3DataParser + }), + children: DP.array(DP.lazy(() => recursive0DataParser)), + tuple: recursive2DataParser, + array: recursive1DataParser, + union: recursive3DataParser +}); + +export const complexRecursiveParserDataParser = recursive0DataParser;" +`; + +exports[`render > renders dataParser with nested checkers 1`] = ` +"import * as DP from "@duplojs/utils/dataParser"; + +export const compactParserDataParser = DP.object({ + name: DP.string({ checkers: [DP.checkerStringMin(2)] }), + roles: DP.array(DP.literal(["admin", "editor"]), { checkers: [DP.checkerArrayMin(1)] }), + contact: DP.union([ + DP.object({ + email: DP.string({ checkers: [DP.checkerEmail()] }) + }), + DP.object({ + phone: DP.string({ checkers: [DP.checkerRegex(/^[+\\d][\\d\\s-]{5,}$/)] }) + }) + ]) +});" +`; + +exports[`render > renders named dependencies before their consumers 1`] = ` +"import * as DP from "@duplojs/utils/dataParser"; + +export const childParserDataParser = DP.string(); + +export const parentParserDataParser = DP.object({ + child: childParserDataParser +});" +`; diff --git a/tests/toDataParser/checkerTransfomers/__snapshots__/arrayMax.test.ts.snap b/tests/toDataParser/checkerTransfomers/__snapshots__/arrayMax.test.ts.snap new file mode 100644 index 0000000..01e3724 --- /dev/null +++ b/tests/toDataParser/checkerTransfomers/__snapshots__/arrayMax.test.ts.snap @@ -0,0 +1,3 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`checkerArrayMax > basic 1`] = `"DP.checkerArrayMax(5)"`; diff --git a/tests/toDataParser/checkerTransfomers/__snapshots__/arrayMin.test.ts.snap b/tests/toDataParser/checkerTransfomers/__snapshots__/arrayMin.test.ts.snap new file mode 100644 index 0000000..7057cfa --- /dev/null +++ b/tests/toDataParser/checkerTransfomers/__snapshots__/arrayMin.test.ts.snap @@ -0,0 +1,3 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`checkerArrayMin > basic 1`] = `"DP.checkerArrayMin(1)"`; diff --git a/tests/toDataParser/checkerTransfomers/__snapshots__/bigintMax.test.ts.snap b/tests/toDataParser/checkerTransfomers/__snapshots__/bigintMax.test.ts.snap new file mode 100644 index 0000000..ad9b3c4 --- /dev/null +++ b/tests/toDataParser/checkerTransfomers/__snapshots__/bigintMax.test.ts.snap @@ -0,0 +1,3 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`checkerBigIntMax > basic 1`] = `"DP.checkerBigIntMax(10n)"`; diff --git a/tests/toDataParser/checkerTransfomers/__snapshots__/bigintMin.test.ts.snap b/tests/toDataParser/checkerTransfomers/__snapshots__/bigintMin.test.ts.snap new file mode 100644 index 0000000..ac627fd --- /dev/null +++ b/tests/toDataParser/checkerTransfomers/__snapshots__/bigintMin.test.ts.snap @@ -0,0 +1,3 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`checkerBigIntMin > basic 1`] = `"DP.checkerBigIntMin(1n)"`; diff --git a/tests/toDataParser/checkerTransfomers/__snapshots__/email.test.ts.snap b/tests/toDataParser/checkerTransfomers/__snapshots__/email.test.ts.snap new file mode 100644 index 0000000..e37e578 --- /dev/null +++ b/tests/toDataParser/checkerTransfomers/__snapshots__/email.test.ts.snap @@ -0,0 +1,3 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`checkerEmail > basic 1`] = `"DP.checkerEmail()"`; diff --git a/tests/toDataParser/checkerTransfomers/__snapshots__/int.test.ts.snap b/tests/toDataParser/checkerTransfomers/__snapshots__/int.test.ts.snap new file mode 100644 index 0000000..8b88003 --- /dev/null +++ b/tests/toDataParser/checkerTransfomers/__snapshots__/int.test.ts.snap @@ -0,0 +1,3 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`checkerInt > basic 1`] = `"DP.checkerInt()"`; diff --git a/tests/toDataParser/checkerTransfomers/__snapshots__/numberMax.test.ts.snap b/tests/toDataParser/checkerTransfomers/__snapshots__/numberMax.test.ts.snap new file mode 100644 index 0000000..60c27e1 --- /dev/null +++ b/tests/toDataParser/checkerTransfomers/__snapshots__/numberMax.test.ts.snap @@ -0,0 +1,5 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`checkerNumberMax > exclusive 1`] = `"DP.checkerNumberMax(10, { exclusive: true })"`; + +exports[`checkerNumberMax > non exclusive 1`] = `"DP.checkerNumberMax(10)"`; diff --git a/tests/toDataParser/checkerTransfomers/__snapshots__/numberMin.test.ts.snap b/tests/toDataParser/checkerTransfomers/__snapshots__/numberMin.test.ts.snap new file mode 100644 index 0000000..85eb900 --- /dev/null +++ b/tests/toDataParser/checkerTransfomers/__snapshots__/numberMin.test.ts.snap @@ -0,0 +1,5 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`checkerNumberMin > exclusive 1`] = `"DP.checkerNumberMin(1, { exclusive: true })"`; + +exports[`checkerNumberMin > non exclusive 1`] = `"DP.checkerNumberMin(1)"`; diff --git a/tests/toDataParser/checkerTransfomers/__snapshots__/refine.test.ts.snap b/tests/toDataParser/checkerTransfomers/__snapshots__/refine.test.ts.snap new file mode 100644 index 0000000..bba7671 --- /dev/null +++ b/tests/toDataParser/checkerTransfomers/__snapshots__/refine.test.ts.snap @@ -0,0 +1,3 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`checkerRefine > basic 1`] = `"DP.checkerRefine((value) => value.length > 0)"`; diff --git a/tests/toDataParser/checkerTransfomers/__snapshots__/regex.test.ts.snap b/tests/toDataParser/checkerTransfomers/__snapshots__/regex.test.ts.snap new file mode 100644 index 0000000..17f4b89 --- /dev/null +++ b/tests/toDataParser/checkerTransfomers/__snapshots__/regex.test.ts.snap @@ -0,0 +1,3 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`checkerRegex > basic 1`] = `"DP.checkerRegex(/^[a-z]+$/)"`; diff --git a/tests/toDataParser/checkerTransfomers/__snapshots__/stringMax.test.ts.snap b/tests/toDataParser/checkerTransfomers/__snapshots__/stringMax.test.ts.snap new file mode 100644 index 0000000..c85d6d7 --- /dev/null +++ b/tests/toDataParser/checkerTransfomers/__snapshots__/stringMax.test.ts.snap @@ -0,0 +1,3 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`checkerStringMax > basic 1`] = `"DP.checkerStringMax(10)"`; diff --git a/tests/toDataParser/checkerTransfomers/__snapshots__/stringMin.test.ts.snap b/tests/toDataParser/checkerTransfomers/__snapshots__/stringMin.test.ts.snap new file mode 100644 index 0000000..8c255a2 --- /dev/null +++ b/tests/toDataParser/checkerTransfomers/__snapshots__/stringMin.test.ts.snap @@ -0,0 +1,3 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`checkerStringMin > basic 1`] = `"DP.checkerStringMin(2)"`; diff --git a/tests/toDataParser/checkerTransfomers/__snapshots__/timeMax.test.ts.snap b/tests/toDataParser/checkerTransfomers/__snapshots__/timeMax.test.ts.snap new file mode 100644 index 0000000..f8ede2d --- /dev/null +++ b/tests/toDataParser/checkerTransfomers/__snapshots__/timeMax.test.ts.snap @@ -0,0 +1,3 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`checkerTimeMax > basic 1`] = `"DP.checkerTimeMax(D.createTime(2000, "millisecond"))"`; diff --git a/tests/toDataParser/checkerTransfomers/__snapshots__/timeMin.test.ts.snap b/tests/toDataParser/checkerTransfomers/__snapshots__/timeMin.test.ts.snap new file mode 100644 index 0000000..7342f22 --- /dev/null +++ b/tests/toDataParser/checkerTransfomers/__snapshots__/timeMin.test.ts.snap @@ -0,0 +1,3 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`checkerTimeMin > basic 1`] = `"DP.checkerTimeMin(D.createTime(1000, "millisecond"))"`; diff --git a/tests/toDataParser/checkerTransfomers/__snapshots__/url.test.ts.snap b/tests/toDataParser/checkerTransfomers/__snapshots__/url.test.ts.snap new file mode 100644 index 0000000..6044bf2 --- /dev/null +++ b/tests/toDataParser/checkerTransfomers/__snapshots__/url.test.ts.snap @@ -0,0 +1,5 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`checkerUrl > with options 1`] = `"DP.checkerUrl({ hostname: /^[a-z.-]+$/, normalize: true, protocol: /^https?$/ })"`; + +exports[`checkerUrl > without options 1`] = `"DP.checkerUrl()"`; diff --git a/tests/toDataParser/checkerTransfomers/__snapshots__/uuid.test.ts.snap b/tests/toDataParser/checkerTransfomers/__snapshots__/uuid.test.ts.snap new file mode 100644 index 0000000..7df9ebf --- /dev/null +++ b/tests/toDataParser/checkerTransfomers/__snapshots__/uuid.test.ts.snap @@ -0,0 +1,3 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`checkerUuid > basic 1`] = `"DP.checkerUuid()"`; diff --git a/tests/toDataParser/checkerTransfomers/arrayMax.test.ts b/tests/toDataParser/checkerTransfomers/arrayMax.test.ts new file mode 100644 index 0000000..c47abd2 --- /dev/null +++ b/tests/toDataParser/checkerTransfomers/arrayMax.test.ts @@ -0,0 +1,18 @@ +import { DataParserToDataParser } from "@scripts/index"; +import { asserts, DP, E, unwrap } from "@duplojs/utils"; +import { printExpression } from "./utils"; + +describe("checkerArrayMax", () => { + it("basic", () => { + const result = DataParserToDataParser.checkerTransformer( + DP.checkerArrayMax(5), + { + transformers: DataParserToDataParser.defaultCheckerTransformers, + importContext: new Map(), + }, + ); + + asserts(result, E.isRight); + expect(printExpression(unwrap(result))).toMatchSnapshot(); + }); +}); diff --git a/tests/toDataParser/checkerTransfomers/arrayMin.test.ts b/tests/toDataParser/checkerTransfomers/arrayMin.test.ts new file mode 100644 index 0000000..41ad63b --- /dev/null +++ b/tests/toDataParser/checkerTransfomers/arrayMin.test.ts @@ -0,0 +1,18 @@ +import { DataParserToDataParser } from "@scripts/index"; +import { asserts, DP, E, unwrap } from "@duplojs/utils"; +import { printExpression } from "./utils"; + +describe("checkerArrayMin", () => { + it("basic", () => { + const result = DataParserToDataParser.checkerTransformer( + DP.checkerArrayMin(1), + { + transformers: DataParserToDataParser.defaultCheckerTransformers, + importContext: new Map(), + }, + ); + + asserts(result, E.isRight); + expect(printExpression(unwrap(result))).toMatchSnapshot(); + }); +}); diff --git a/tests/toDataParser/checkerTransfomers/bigintMax.test.ts b/tests/toDataParser/checkerTransfomers/bigintMax.test.ts new file mode 100644 index 0000000..4134783 --- /dev/null +++ b/tests/toDataParser/checkerTransfomers/bigintMax.test.ts @@ -0,0 +1,18 @@ +import { DataParserToDataParser } from "@scripts/index"; +import { asserts, DP, E, unwrap } from "@duplojs/utils"; +import { printExpression } from "./utils"; + +describe("checkerBigIntMax", () => { + it("basic", () => { + const result = DataParserToDataParser.checkerTransformer( + DP.checkerBigIntMax(10n), + { + transformers: DataParserToDataParser.defaultCheckerTransformers, + importContext: new Map(), + }, + ); + + asserts(result, E.isRight); + expect(printExpression(unwrap(result))).toMatchSnapshot(); + }); +}); diff --git a/tests/toDataParser/checkerTransfomers/bigintMin.test.ts b/tests/toDataParser/checkerTransfomers/bigintMin.test.ts new file mode 100644 index 0000000..c7e0e3c --- /dev/null +++ b/tests/toDataParser/checkerTransfomers/bigintMin.test.ts @@ -0,0 +1,18 @@ +import { DataParserToDataParser } from "@scripts/index"; +import { asserts, DP, E, unwrap } from "@duplojs/utils"; +import { printExpression } from "./utils"; + +describe("checkerBigIntMin", () => { + it("basic", () => { + const result = DataParserToDataParser.checkerTransformer( + DP.checkerBigIntMin(1n), + { + transformers: DataParserToDataParser.defaultCheckerTransformers, + importContext: new Map(), + }, + ); + + asserts(result, E.isRight); + expect(printExpression(unwrap(result))).toMatchSnapshot(); + }); +}); diff --git a/tests/toDataParser/checkerTransfomers/email.test.ts b/tests/toDataParser/checkerTransfomers/email.test.ts new file mode 100644 index 0000000..d894e5c --- /dev/null +++ b/tests/toDataParser/checkerTransfomers/email.test.ts @@ -0,0 +1,18 @@ +import { DataParserToDataParser } from "@scripts/index"; +import { asserts, DP, E, unwrap } from "@duplojs/utils"; +import { printExpression } from "./utils"; + +describe("checkerEmail", () => { + it("basic", () => { + const result = DataParserToDataParser.checkerTransformer( + DP.checkerEmail(), + { + transformers: DataParserToDataParser.defaultCheckerTransformers, + importContext: new Map(), + }, + ); + + asserts(result, E.isRight); + expect(printExpression(unwrap(result))).toMatchSnapshot(); + }); +}); diff --git a/tests/toDataParser/checkerTransfomers/int.test.ts b/tests/toDataParser/checkerTransfomers/int.test.ts new file mode 100644 index 0000000..e1c868f --- /dev/null +++ b/tests/toDataParser/checkerTransfomers/int.test.ts @@ -0,0 +1,18 @@ +import { DataParserToDataParser } from "@scripts/index"; +import { asserts, DP, E, unwrap } from "@duplojs/utils"; +import { printExpression } from "./utils"; + +describe("checkerInt", () => { + it("basic", () => { + const result = DataParserToDataParser.checkerTransformer( + DP.checkerInt(), + { + transformers: DataParserToDataParser.defaultCheckerTransformers, + importContext: new Map(), + }, + ); + + asserts(result, E.isRight); + expect(printExpression(unwrap(result))).toMatchSnapshot(); + }); +}); diff --git a/tests/toDataParser/checkerTransfomers/numberMax.test.ts b/tests/toDataParser/checkerTransfomers/numberMax.test.ts new file mode 100644 index 0000000..c07e13d --- /dev/null +++ b/tests/toDataParser/checkerTransfomers/numberMax.test.ts @@ -0,0 +1,31 @@ +import { DataParserToDataParser } from "@scripts/index"; +import { asserts, DP, E, unwrap } from "@duplojs/utils"; +import { printExpression } from "./utils"; + +describe("checkerNumberMax", () => { + it("exclusive", () => { + const result = DataParserToDataParser.checkerTransformer( + DP.checkerNumberMax(10, { exclusive: true }), + { + transformers: DataParserToDataParser.defaultCheckerTransformers, + importContext: new Map(), + }, + ); + + asserts(result, E.isRight); + expect(printExpression(unwrap(result))).toMatchSnapshot(); + }); + + it("non exclusive", () => { + const result = DataParserToDataParser.checkerTransformer( + DP.checkerNumberMax(10), + { + transformers: DataParserToDataParser.defaultCheckerTransformers, + importContext: new Map(), + }, + ); + + asserts(result, E.isRight); + expect(printExpression(unwrap(result))).toMatchSnapshot(); + }); +}); diff --git a/tests/toDataParser/checkerTransfomers/numberMin.test.ts b/tests/toDataParser/checkerTransfomers/numberMin.test.ts new file mode 100644 index 0000000..1f24151 --- /dev/null +++ b/tests/toDataParser/checkerTransfomers/numberMin.test.ts @@ -0,0 +1,31 @@ +import { DataParserToDataParser } from "@scripts/index"; +import { asserts, DP, E, unwrap } from "@duplojs/utils"; +import { printExpression } from "./utils"; + +describe("checkerNumberMin", () => { + it("exclusive", () => { + const result = DataParserToDataParser.checkerTransformer( + DP.checkerNumberMin(1, { exclusive: true }), + { + transformers: DataParserToDataParser.defaultCheckerTransformers, + importContext: new Map(), + }, + ); + + asserts(result, E.isRight); + expect(printExpression(unwrap(result))).toMatchSnapshot(); + }); + + it("non exclusive", () => { + const result = DataParserToDataParser.checkerTransformer( + DP.checkerNumberMin(1), + { + transformers: DataParserToDataParser.defaultCheckerTransformers, + importContext: new Map(), + }, + ); + + asserts(result, E.isRight); + expect(printExpression(unwrap(result))).toMatchSnapshot(); + }); +}); diff --git a/tests/toDataParser/checkerTransfomers/refine.test.ts b/tests/toDataParser/checkerTransfomers/refine.test.ts new file mode 100644 index 0000000..9e0650d --- /dev/null +++ b/tests/toDataParser/checkerTransfomers/refine.test.ts @@ -0,0 +1,131 @@ +/* eslint-disable @typescript-eslint/consistent-type-imports */ +import { DataParserToDataParser } from "@scripts/index"; +import { asserts, DP, E, unwrap } from "@duplojs/utils"; +import { afterEach, vi } from "vitest"; +import { printExpression } from "./utils"; + +afterEach(() => { + vi.resetModules(); + vi.doUnmock("typescript"); +}); + +describe("checkerRefine", () => { + it("basic", () => { + const checker = DP.checkerRefine((value: number[]) => value.length > 0); + const result = DataParserToDataParser.checkerTransformer( + checker, + { + transformers: DataParserToDataParser.defaultCheckerTransformers, + importContext: new Map(), + }, + ); + + asserts(result, E.isRight); + expect(printExpression(unwrap(result))).toMatchSnapshot(); + }); + + it("native function error", () => { + const checker = DP.checkerRefine(Array.isArray as never); + const result = DataParserToDataParser.checkerTransformer( + checker, + { + transformers: DataParserToDataParser.defaultCheckerTransformers, + importContext: new Map(), + }, + ); + + expect(result).toEqual(E.left("buildCheckerError", checker)); + }); + + it("invalid source statement error", () => { + const checker = DP.checkerRefine(() => true); + (checker.definition.theFunction as { toString(): string }).toString = () => ";"; + + const result = DataParserToDataParser.checkerTransformer( + checker, + { + transformers: DataParserToDataParser.defaultCheckerTransformers, + importContext: new Map(), + }, + ); + + expect(result).toEqual(E.left("buildCheckerError", checker)); + }); + + it("non function expression error", () => { + const checker = DP.checkerRefine(() => true); + (checker.definition.theFunction as { toString(): string }).toString = () => "1 + 1"; + + const result = DataParserToDataParser.checkerTransformer( + checker, + { + transformers: DataParserToDataParser.defaultCheckerTransformers, + importContext: new Map(), + }, + ); + + expect(result).toEqual(E.left("buildCheckerError", checker)); + }); + + it("error when sourceFile has no statement", async() => { + vi.doMock("typescript", async() => { + const actual = await vi.importActual("typescript"); + return { + ...actual, + createSourceFile() { + return { statements: [] }; + }, + }; + }); + + const { checkerRefineTransformer } = await import("@scripts/toDataParser/checkerTransformer/defaults/refine"); + const checker = DP.checkerRefine(() => true); + const importContext = new Map(); + const result = checkerRefineTransformer(checker, { + importContext, + success(value) { + return E.right("buildSuccess", value); + }, + buildError() { + return E.left("buildCheckerError", checker); + }, + addImport() {}, + getDefinition() { + return []; + }, + }); + + expect(result).toEqual(E.left("buildCheckerError", checker)); + }); + + it("error when expression is not parenthesized", async() => { + vi.doMock("typescript", async() => { + const actual = await vi.importActual("typescript"); + return { + ...actual, + isParenthesizedExpression() { + return false; + }, + }; + }); + + const { checkerRefineTransformer } = await import("@scripts/toDataParser/checkerTransformer/defaults/refine"); + const checker = DP.checkerRefine(() => true); + const importContext = new Map(); + const result = checkerRefineTransformer(checker, { + importContext, + success(value) { + return E.right("buildSuccess", value); + }, + buildError() { + return E.left("buildCheckerError", checker); + }, + addImport() {}, + getDefinition() { + return []; + }, + }); + + expect(result).toEqual(E.left("buildCheckerError", checker)); + }); +}); diff --git a/tests/toDataParser/checkerTransfomers/regex.test.ts b/tests/toDataParser/checkerTransfomers/regex.test.ts new file mode 100644 index 0000000..e0967bf --- /dev/null +++ b/tests/toDataParser/checkerTransfomers/regex.test.ts @@ -0,0 +1,18 @@ +import { DataParserToDataParser } from "@scripts/index"; +import { asserts, DP, E, unwrap } from "@duplojs/utils"; +import { printExpression } from "./utils"; + +describe("checkerRegex", () => { + it("basic", () => { + const result = DataParserToDataParser.checkerTransformer( + DP.checkerRegex(/^[a-z]+$/), + { + transformers: DataParserToDataParser.defaultCheckerTransformers, + importContext: new Map(), + }, + ); + + asserts(result, E.isRight); + expect(printExpression(unwrap(result))).toMatchSnapshot(); + }); +}); diff --git a/tests/toDataParser/checkerTransfomers/stringMax.test.ts b/tests/toDataParser/checkerTransfomers/stringMax.test.ts new file mode 100644 index 0000000..4591143 --- /dev/null +++ b/tests/toDataParser/checkerTransfomers/stringMax.test.ts @@ -0,0 +1,18 @@ +import { DataParserToDataParser } from "@scripts/index"; +import { asserts, DP, E, unwrap } from "@duplojs/utils"; +import { printExpression } from "./utils"; + +describe("checkerStringMax", () => { + it("basic", () => { + const result = DataParserToDataParser.checkerTransformer( + DP.checkerStringMax(10), + { + transformers: DataParserToDataParser.defaultCheckerTransformers, + importContext: new Map(), + }, + ); + + asserts(result, E.isRight); + expect(printExpression(unwrap(result))).toMatchSnapshot(); + }); +}); diff --git a/tests/toDataParser/checkerTransfomers/stringMin.test.ts b/tests/toDataParser/checkerTransfomers/stringMin.test.ts new file mode 100644 index 0000000..4c5d594 --- /dev/null +++ b/tests/toDataParser/checkerTransfomers/stringMin.test.ts @@ -0,0 +1,18 @@ +import { DataParserToDataParser } from "@scripts/index"; +import { asserts, DP, E, unwrap } from "@duplojs/utils"; +import { printExpression } from "./utils"; + +describe("checkerStringMin", () => { + it("basic", () => { + const result = DataParserToDataParser.checkerTransformer( + DP.checkerStringMin(2), + { + transformers: DataParserToDataParser.defaultCheckerTransformers, + importContext: new Map(), + }, + ); + + asserts(result, E.isRight); + expect(printExpression(unwrap(result))).toMatchSnapshot(); + }); +}); diff --git a/tests/toDataParser/checkerTransfomers/timeMax.test.ts b/tests/toDataParser/checkerTransfomers/timeMax.test.ts new file mode 100644 index 0000000..354f49b --- /dev/null +++ b/tests/toDataParser/checkerTransfomers/timeMax.test.ts @@ -0,0 +1,22 @@ +import { DataParserToDataParser } from "@scripts/index"; +import { asserts, DDate, DP, E, unwrap } from "@duplojs/utils"; +import { printExpression } from "./utils"; + +describe("checkerTimeMax", () => { + it("basic", () => { + const importContext = new Map(); + const result = DataParserToDataParser.checkerTransformer( + DP.checkerTimeMax(DDate.createTime(2000, "millisecond")), + { + transformers: DataParserToDataParser.defaultCheckerTransformers, + importContext, + }, + ); + + asserts(result, E.isRight); + expect(importContext.get("@duplojs/utils/date")).toStrictEqual({ + namespace: ["D"], + }); + expect(printExpression(unwrap(result))).toMatchSnapshot(); + }); +}); diff --git a/tests/toDataParser/checkerTransfomers/timeMin.test.ts b/tests/toDataParser/checkerTransfomers/timeMin.test.ts new file mode 100644 index 0000000..3160b63 --- /dev/null +++ b/tests/toDataParser/checkerTransfomers/timeMin.test.ts @@ -0,0 +1,22 @@ +import { DataParserToDataParser } from "@scripts/index"; +import { asserts, DDate, DP, E, unwrap } from "@duplojs/utils"; +import { printExpression } from "./utils"; + +describe("checkerTimeMin", () => { + it("basic", () => { + const importContext = new Map(); + const result = DataParserToDataParser.checkerTransformer( + DP.checkerTimeMin(DDate.createTime(1000, "millisecond")), + { + transformers: DataParserToDataParser.defaultCheckerTransformers, + importContext, + }, + ); + + asserts(result, E.isRight); + expect(importContext.get("@duplojs/utils/date")).toStrictEqual({ + namespace: ["D"], + }); + expect(printExpression(unwrap(result))).toMatchSnapshot(); + }); +}); diff --git a/tests/toDataParser/checkerTransfomers/url.test.ts b/tests/toDataParser/checkerTransfomers/url.test.ts new file mode 100644 index 0000000..1438098 --- /dev/null +++ b/tests/toDataParser/checkerTransfomers/url.test.ts @@ -0,0 +1,35 @@ +import { DataParserToDataParser } from "@scripts/index"; +import { asserts, DP, E, unwrap } from "@duplojs/utils"; +import { printExpression } from "./utils"; + +describe("checkerUrl", () => { + it("with options", () => { + const result = DataParserToDataParser.checkerTransformer( + DP.checkerUrl({ + hostname: /^[a-z.-]+$/, + normalize: true, + protocol: /^https?$/, + }), + { + transformers: DataParserToDataParser.defaultCheckerTransformers, + importContext: new Map(), + }, + ); + + asserts(result, E.isRight); + expect(printExpression(unwrap(result))).toMatchSnapshot(); + }); + + it("without options", () => { + const result = DataParserToDataParser.checkerTransformer( + DP.checkerUrl(), + { + transformers: DataParserToDataParser.defaultCheckerTransformers, + importContext: new Map(), + }, + ); + + asserts(result, E.isRight); + expect(printExpression(unwrap(result))).toMatchSnapshot(); + }); +}); diff --git a/tests/toDataParser/checkerTransfomers/utils.ts b/tests/toDataParser/checkerTransfomers/utils.ts new file mode 100644 index 0000000..5209fbb --- /dev/null +++ b/tests/toDataParser/checkerTransfomers/utils.ts @@ -0,0 +1,7 @@ +import { createPrinter, createSourceFile, EmitHint, ScriptKind, ScriptTarget, type CallExpression } from "typescript"; + +export function printExpression(expression: CallExpression) { + const printer = createPrinter(); + const sourceFile = createSourceFile("test.ts", "", ScriptTarget.Latest, false, ScriptKind.TS); + return printer.printNode(EmitHint.Unspecified, expression, sourceFile); +} diff --git a/tests/toDataParser/checkerTransfomers/uuid.test.ts b/tests/toDataParser/checkerTransfomers/uuid.test.ts new file mode 100644 index 0000000..df7674c --- /dev/null +++ b/tests/toDataParser/checkerTransfomers/uuid.test.ts @@ -0,0 +1,18 @@ +import { DataParserToDataParser } from "@scripts/index"; +import { asserts, DP, E, unwrap } from "@duplojs/utils"; +import { printExpression } from "./utils"; + +describe("checkerUuid", () => { + it("basic", () => { + const result = DataParserToDataParser.checkerTransformer( + DP.checkerUuid(), + { + transformers: DataParserToDataParser.defaultCheckerTransformers, + importContext: new Map(), + }, + ); + + asserts(result, E.isRight); + expect(printExpression(unwrap(result))).toMatchSnapshot(); + }); +}); diff --git a/tests/toDataParser/dataParserTransformer/__snapshots__/array.test.ts.snap b/tests/toDataParser/dataParserTransformer/__snapshots__/array.test.ts.snap new file mode 100644 index 0000000..f3bee21 --- /dev/null +++ b/tests/toDataParser/dataParserTransformer/__snapshots__/array.test.ts.snap @@ -0,0 +1,7 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`array > renders array parser 1`] = ` +"import * as DP from "@duplojs/utils/dataParser"; + +export const arrayParserDataParser = DP.array(DP.string());" +`; diff --git a/tests/toDataParser/dataParserTransformer/__snapshots__/bigint.test.ts.snap b/tests/toDataParser/dataParserTransformer/__snapshots__/bigint.test.ts.snap new file mode 100644 index 0000000..90c65c2 --- /dev/null +++ b/tests/toDataParser/dataParserTransformer/__snapshots__/bigint.test.ts.snap @@ -0,0 +1,13 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`bigint > renders bigint parser 1`] = ` +"import * as DP from "@duplojs/utils/dataParser"; + +export const bigintParserDataParser = DP.bigint({ coerce: true });" +`; + +exports[`bigint > renders bigint parser without coerce 1`] = ` +"import * as DP from "@duplojs/utils/dataParser"; + +export const bigintParserNoCoerceDataParser = DP.bigint();" +`; diff --git a/tests/toDataParser/dataParserTransformer/__snapshots__/boolean.test.ts.snap b/tests/toDataParser/dataParserTransformer/__snapshots__/boolean.test.ts.snap new file mode 100644 index 0000000..cd1f044 --- /dev/null +++ b/tests/toDataParser/dataParserTransformer/__snapshots__/boolean.test.ts.snap @@ -0,0 +1,13 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`boolean > renders boolean parser 1`] = ` +"import * as DP from "@duplojs/utils/dataParser"; + +export const booleanParserDataParser = DP.boolean({ coerce: true });" +`; + +exports[`boolean > renders boolean parser without coerce 1`] = ` +"import * as DP from "@duplojs/utils/dataParser"; + +export const booleanParserNoCoerceDataParser = DP.boolean();" +`; diff --git a/tests/toDataParser/dataParserTransformer/__snapshots__/date.test.ts.snap b/tests/toDataParser/dataParserTransformer/__snapshots__/date.test.ts.snap new file mode 100644 index 0000000..a3816c5 --- /dev/null +++ b/tests/toDataParser/dataParserTransformer/__snapshots__/date.test.ts.snap @@ -0,0 +1,13 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`date > renders date parser 1`] = ` +"import * as DP from "@duplojs/utils/dataParser"; + +export const dateParserDataParser = DP.date({ coerce: true });" +`; + +exports[`date > renders date parser without coerce 1`] = ` +"import * as DP from "@duplojs/utils/dataParser"; + +export const dateParserNoCoerceDataParser = DP.date();" +`; diff --git a/tests/toDataParser/dataParserTransformer/__snapshots__/empty.test.ts.snap b/tests/toDataParser/dataParserTransformer/__snapshots__/empty.test.ts.snap new file mode 100644 index 0000000..f75612f --- /dev/null +++ b/tests/toDataParser/dataParserTransformer/__snapshots__/empty.test.ts.snap @@ -0,0 +1,13 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`empty > renders empty parser 1`] = ` +"import * as DP from "@duplojs/utils/dataParser"; + +export const emptyParserDataParser = DP.empty({ coerce: true });" +`; + +exports[`empty > renders empty parser without coerce 1`] = ` +"import * as DP from "@duplojs/utils/dataParser"; + +export const emptyParserNoCoerceDataParser = DP.empty();" +`; diff --git a/tests/toDataParser/dataParserTransformer/__snapshots__/file.test.ts.snap b/tests/toDataParser/dataParserTransformer/__snapshots__/file.test.ts.snap new file mode 100644 index 0000000..faaa027 --- /dev/null +++ b/tests/toDataParser/dataParserTransformer/__snapshots__/file.test.ts.snap @@ -0,0 +1,33 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`file > renders extended file parser namespace 1`] = ` +"import * as DP from "@duplojs/utils/dataParser"; + +import * as DPE from "@duplojs/utils/dataParserExtended"; + +import * as SDPE from "@duplojs/server-utils/dataParserExtended"; + +export const fileParserExtendedDataParser = SDPE.file({});" +`; + +exports[`file > renders file parser with async constraints 1`] = ` +"import * as DP from "@duplojs/utils/dataParser"; + +import * as SDP from "@duplojs/server-utils/dataParser"; + +export const fileParserDataParser = SDP.file({ + coerce: true, + checkExist: true, + maxSize: 10, + minSize: 5, + mimeType: /image\\/png/ +});" +`; + +exports[`file > renders file parser without options 1`] = ` +"import * as DP from "@duplojs/utils/dataParser"; + +import * as SDP from "@duplojs/server-utils/dataParser"; + +export const fileParserNoOptionsDataParser = SDP.file({});" +`; diff --git a/tests/toDataParser/dataParserTransformer/__snapshots__/lazy.test.ts.snap b/tests/toDataParser/dataParserTransformer/__snapshots__/lazy.test.ts.snap new file mode 100644 index 0000000..9dee181 --- /dev/null +++ b/tests/toDataParser/dataParserTransformer/__snapshots__/lazy.test.ts.snap @@ -0,0 +1,7 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`lazy > renders lazy parser 1`] = ` +"import * as DP from "@duplojs/utils/dataParser"; + +export const lazyParserDataParser = DP.lazy(() => DP.string());" +`; diff --git a/tests/toDataParser/dataParserTransformer/__snapshots__/literal.test.ts.snap b/tests/toDataParser/dataParserTransformer/__snapshots__/literal.test.ts.snap new file mode 100644 index 0000000..fe1bbab --- /dev/null +++ b/tests/toDataParser/dataParserTransformer/__snapshots__/literal.test.ts.snap @@ -0,0 +1,15 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`literal > renders literal parser 1`] = ` +"import * as DP from "@duplojs/utils/dataParser"; + +export const literalParserDataParser = DP.literal([ + "foo", + 1, + 1n, + true, + false, + null, + undefined +]);" +`; diff --git a/tests/toDataParser/dataParserTransformer/__snapshots__/nil.test.ts.snap b/tests/toDataParser/dataParserTransformer/__snapshots__/nil.test.ts.snap new file mode 100644 index 0000000..7a45b75 --- /dev/null +++ b/tests/toDataParser/dataParserTransformer/__snapshots__/nil.test.ts.snap @@ -0,0 +1,13 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`nil > renders nil parser 1`] = ` +"import * as DP from "@duplojs/utils/dataParser"; + +export const nilParserDataParser = DP.nil({ coerce: true });" +`; + +exports[`nil > renders nil parser without coerce 1`] = ` +"import * as DP from "@duplojs/utils/dataParser"; + +export const nilParserNoCoerceDataParser = DP.nil();" +`; diff --git a/tests/toDataParser/dataParserTransformer/__snapshots__/nullable.test.ts.snap b/tests/toDataParser/dataParserTransformer/__snapshots__/nullable.test.ts.snap new file mode 100644 index 0000000..14c7c85 --- /dev/null +++ b/tests/toDataParser/dataParserTransformer/__snapshots__/nullable.test.ts.snap @@ -0,0 +1,7 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`nullable > renders nullable parser 1`] = ` +"import * as DP from "@duplojs/utils/dataParser"; + +export const nullableParserDataParser = DP.nullable(DP.string());" +`; diff --git a/tests/toDataParser/dataParserTransformer/__snapshots__/number.test.ts.snap b/tests/toDataParser/dataParserTransformer/__snapshots__/number.test.ts.snap new file mode 100644 index 0000000..6a1ab0e --- /dev/null +++ b/tests/toDataParser/dataParserTransformer/__snapshots__/number.test.ts.snap @@ -0,0 +1,13 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`number > renders number parser 1`] = ` +"import * as DP from "@duplojs/utils/dataParser"; + +export const numberParserDataParser = DP.number({ coerce: true });" +`; + +exports[`number > renders number parser without coerce 1`] = ` +"import * as DP from "@duplojs/utils/dataParser"; + +export const numberParserNoCoerceDataParser = DP.number();" +`; diff --git a/tests/toDataParser/dataParserTransformer/__snapshots__/object.test.ts.snap b/tests/toDataParser/dataParserTransformer/__snapshots__/object.test.ts.snap new file mode 100644 index 0000000..4b6b2f1 --- /dev/null +++ b/tests/toDataParser/dataParserTransformer/__snapshots__/object.test.ts.snap @@ -0,0 +1,10 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`object > renders object parser 1`] = ` +"import * as DP from "@duplojs/utils/dataParser"; + +export const objectParserDataParser = DP.object({ + foo: DP.string(), + bar: DP.number() +});" +`; diff --git a/tests/toDataParser/dataParserTransformer/__snapshots__/optional.test.ts.snap b/tests/toDataParser/dataParserTransformer/__snapshots__/optional.test.ts.snap new file mode 100644 index 0000000..5d9a245 --- /dev/null +++ b/tests/toDataParser/dataParserTransformer/__snapshots__/optional.test.ts.snap @@ -0,0 +1,7 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`optional > renders optional parser 1`] = ` +"import * as DP from "@duplojs/utils/dataParser"; + +export const optionalParserDataParser = DP.optional(DP.string());" +`; diff --git a/tests/toDataParser/dataParserTransformer/__snapshots__/pipe.test.ts.snap b/tests/toDataParser/dataParserTransformer/__snapshots__/pipe.test.ts.snap new file mode 100644 index 0000000..bdbff94 --- /dev/null +++ b/tests/toDataParser/dataParserTransformer/__snapshots__/pipe.test.ts.snap @@ -0,0 +1,7 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`pipe > renders pipe parser 1`] = ` +"import * as DP from "@duplojs/utils/dataParser"; + +export const pipeParserDataParser = DP.pipe(DP.string(), DP.number());" +`; diff --git a/tests/toDataParser/dataParserTransformer/__snapshots__/record.test.ts.snap b/tests/toDataParser/dataParserTransformer/__snapshots__/record.test.ts.snap new file mode 100644 index 0000000..14f0def --- /dev/null +++ b/tests/toDataParser/dataParserTransformer/__snapshots__/record.test.ts.snap @@ -0,0 +1,7 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`record > renders record parser 1`] = ` +"import * as DP from "@duplojs/utils/dataParser"; + +export const recordParserDataParser = DP.record(DP.string(), DP.number());" +`; diff --git a/tests/toDataParser/dataParserTransformer/__snapshots__/recover.test.ts.snap b/tests/toDataParser/dataParserTransformer/__snapshots__/recover.test.ts.snap new file mode 100644 index 0000000..b6db4cf --- /dev/null +++ b/tests/toDataParser/dataParserTransformer/__snapshots__/recover.test.ts.snap @@ -0,0 +1,7 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`recover > renders inner parser 1`] = ` +"import * as DP from "@duplojs/utils/dataParser"; + +export const recoverParserDataParser = DP.string();" +`; diff --git a/tests/toDataParser/dataParserTransformer/__snapshots__/string.test.ts.snap b/tests/toDataParser/dataParserTransformer/__snapshots__/string.test.ts.snap new file mode 100644 index 0000000..e0106a5 --- /dev/null +++ b/tests/toDataParser/dataParserTransformer/__snapshots__/string.test.ts.snap @@ -0,0 +1,7 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`string > renders string parser 1`] = ` +"import * as DP from "@duplojs/utils/dataParser"; + +export const stringParserDataParser = DP.string();" +`; diff --git a/tests/toDataParser/dataParserTransformer/__snapshots__/templateLiteral.test.ts.snap b/tests/toDataParser/dataParserTransformer/__snapshots__/templateLiteral.test.ts.snap new file mode 100644 index 0000000..37779af --- /dev/null +++ b/tests/toDataParser/dataParserTransformer/__snapshots__/templateLiteral.test.ts.snap @@ -0,0 +1,24 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`templateLiteral > renders template literal parser 1`] = ` +"import * as DP from "@duplojs/utils/dataParser"; + +export const templateLiteralParserDataParser = DP.templateLiteral([ + "pre-", + 1n, + "mid-", + true, + "-false-", + false, + "-str-", + "abc", + "-num-", + 42, + "-null-", + null, + "-undef-", + undefined, + "-dp-", + DP.string() +]);" +`; diff --git a/tests/toDataParser/dataParserTransformer/__snapshots__/time.test.ts.snap b/tests/toDataParser/dataParserTransformer/__snapshots__/time.test.ts.snap new file mode 100644 index 0000000..76650b7 --- /dev/null +++ b/tests/toDataParser/dataParserTransformer/__snapshots__/time.test.ts.snap @@ -0,0 +1,18 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`time > renders time parser with checker imports 1`] = ` +"import * as DP from "@duplojs/utils/dataParser"; + +import * as D from "@duplojs/utils/date"; + +export const timeParserDataParser = DP.time({ + coerce: true, + checkers: [DP.checkerTimeMin(D.createTime(1, "millisecond"))] +});" +`; + +exports[`time > renders time parser without coerce and without checkers 1`] = ` +"import * as DP from "@duplojs/utils/dataParser"; + +export const timeParserNoCoerceDataParser = DP.time();" +`; diff --git a/tests/toDataParser/dataParserTransformer/__snapshots__/transform.test.ts.snap b/tests/toDataParser/dataParserTransformer/__snapshots__/transform.test.ts.snap new file mode 100644 index 0000000..e15db97 --- /dev/null +++ b/tests/toDataParser/dataParserTransformer/__snapshots__/transform.test.ts.snap @@ -0,0 +1,7 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`transform > renders transform parser 1`] = ` +"import * as DP from "@duplojs/utils/dataParser"; + +export const transformParserDataParser = DP.transform(DP.string(), (value) => value.trim());" +`; diff --git a/tests/toDataParser/dataParserTransformer/__snapshots__/tuple.test.ts.snap b/tests/toDataParser/dataParserTransformer/__snapshots__/tuple.test.ts.snap new file mode 100644 index 0000000..5dbbd5f --- /dev/null +++ b/tests/toDataParser/dataParserTransformer/__snapshots__/tuple.test.ts.snap @@ -0,0 +1,13 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`tuple > renders tuple parser with rest 1`] = ` +"import * as DP from "@duplojs/utils/dataParser"; + +export const tupleParserDataParser = DP.tuple([DP.string()], { rest: DP.number() });" +`; + +exports[`tuple > renders tuple parser without rest 1`] = ` +"import * as DP from "@duplojs/utils/dataParser"; + +export const tupleParserNoRestDataParser = DP.tuple([DP.string()]);" +`; diff --git a/tests/toDataParser/dataParserTransformer/__snapshots__/union.test.ts.snap b/tests/toDataParser/dataParserTransformer/__snapshots__/union.test.ts.snap new file mode 100644 index 0000000..ace7d05 --- /dev/null +++ b/tests/toDataParser/dataParserTransformer/__snapshots__/union.test.ts.snap @@ -0,0 +1,10 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`union > renders union parser 1`] = ` +"import * as DP from "@duplojs/utils/dataParser"; + +export const unionParserDataParser = DP.union([ + DP.string(), + DP.number() +]);" +`; diff --git a/tests/toDataParser/dataParserTransformer/__snapshots__/unknown.test.ts.snap b/tests/toDataParser/dataParserTransformer/__snapshots__/unknown.test.ts.snap new file mode 100644 index 0000000..fe8cbe6 --- /dev/null +++ b/tests/toDataParser/dataParserTransformer/__snapshots__/unknown.test.ts.snap @@ -0,0 +1,7 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`unknown > renders unknown parser 1`] = ` +"import * as DP from "@duplojs/utils/dataParser"; + +export const unknownParserDataParser = DP.unknown();" +`; diff --git a/tests/toDataParser/dataParserTransformer/array.test.ts b/tests/toDataParser/dataParserTransformer/array.test.ts new file mode 100644 index 0000000..0f9cb81 --- /dev/null +++ b/tests/toDataParser/dataParserTransformer/array.test.ts @@ -0,0 +1,60 @@ +import { DP, DPE, E } from "@duplojs/utils"; +import { defaultTransformers, defaultCheckerTransformers, render } from "@scripts/toDataParser"; +import { defaultTransformers as tsDefaultTransformers } from "@scripts/toTypescript"; + +describe("array", () => { + it("renders array parser", () => { + expect( + render( + DPE.array(DPE.string()), + { + identifier: "arrayParser", + dataParserTransformers: defaultTransformers, + checkerTransformers: defaultCheckerTransformers, + typescriptTransformers: tsDefaultTransformers, + }, + ), + ).toMatchSnapshot(); + }); + + it("fails when inner element cannot be rendered", () => { + expect( + () => render( + DPE.array(DPE.string()), + { + identifier: "arrayParserError", + dataParserTransformers: [ + ((dataParser, { buildError }) => DP.stringKind.has(dataParser) + ? buildError() + : E.left("dataParserNotSupport", dataParser)), + ...defaultTransformers, + ], + checkerTransformers: defaultCheckerTransformers, + typescriptTransformers: tsDefaultTransformers, + }, + ), + ).toThrow(); + }); + + it("fails when definition checker cannot be rendered", () => { + const schema = DPE.array(DPE.string(), { + checkers: [{ kind: "forced-error" } as any], + }); + + expect( + () => render( + schema, + { + identifier: "arrayParserCheckerError", + dataParserTransformers: defaultTransformers, + checkerTransformers: [ + ((checker, { buildError }) => (checker as any).kind === "forced-error" + ? buildError() + : E.left("checkerNotSupport", checker)), + ], + typescriptTransformers: tsDefaultTransformers, + }, + ), + ).toThrow(); + }); +}); diff --git a/tests/toDataParser/dataParserTransformer/bigint.test.ts b/tests/toDataParser/dataParserTransformer/bigint.test.ts new file mode 100644 index 0000000..f2e7458 --- /dev/null +++ b/tests/toDataParser/dataParserTransformer/bigint.test.ts @@ -0,0 +1,55 @@ +import { DPE, E } from "@duplojs/utils"; +import { defaultTransformers, defaultCheckerTransformers, render } from "@scripts/toDataParser"; +import { defaultTransformers as tsDefaultTransformers } from "@scripts/toTypescript"; + +describe("bigint", () => { + it("renders bigint parser", () => { + expect( + render( + DPE.bigint({ coerce: true }), + { + identifier: "bigintParser", + dataParserTransformers: defaultTransformers, + checkerTransformers: defaultCheckerTransformers, + typescriptTransformers: tsDefaultTransformers, + }, + ), + ).toMatchSnapshot(); + }); + + it("renders bigint parser without coerce", () => { + expect( + render( + DPE.bigint(), + { + identifier: "bigintParserNoCoerce", + dataParserTransformers: defaultTransformers, + checkerTransformers: defaultCheckerTransformers, + typescriptTransformers: tsDefaultTransformers, + }, + ), + ).toMatchSnapshot(); + }); + + it("fails when definition checker cannot be rendered", () => { + const schema = DPE.bigint({ + checkers: [{ kind: "forced-error" } as any], + }); + + expect( + () => render( + schema, + { + identifier: "bigintParserError", + dataParserTransformers: defaultTransformers, + checkerTransformers: [ + ((checker, { buildError }) => (checker as any).kind === "forced-error" + ? buildError() + : E.left("checkerNotSupport", checker)), + ], + typescriptTransformers: tsDefaultTransformers, + }, + ), + ).toThrow(); + }); +}); diff --git a/tests/toDataParser/dataParserTransformer/boolean.test.ts b/tests/toDataParser/dataParserTransformer/boolean.test.ts new file mode 100644 index 0000000..6464665 --- /dev/null +++ b/tests/toDataParser/dataParserTransformer/boolean.test.ts @@ -0,0 +1,55 @@ +import { DPE, E } from "@duplojs/utils"; +import { defaultTransformers, defaultCheckerTransformers, render } from "@scripts/toDataParser"; +import { defaultTransformers as tsDefaultTransformers } from "@scripts/toTypescript"; + +describe("boolean", () => { + it("renders boolean parser", () => { + expect( + render( + DPE.boolean({ coerce: true }), + { + identifier: "booleanParser", + dataParserTransformers: defaultTransformers, + checkerTransformers: defaultCheckerTransformers, + typescriptTransformers: tsDefaultTransformers, + }, + ), + ).toMatchSnapshot(); + }); + + it("renders boolean parser without coerce", () => { + expect( + render( + DPE.boolean(), + { + identifier: "booleanParserNoCoerce", + dataParserTransformers: defaultTransformers, + checkerTransformers: defaultCheckerTransformers, + typescriptTransformers: tsDefaultTransformers, + }, + ), + ).toMatchSnapshot(); + }); + + it("fails when definition checker cannot be rendered", () => { + const schema = DPE.boolean({ + checkers: [{ kind: "forced-error" } as any], + }); + + expect( + () => render( + schema, + { + identifier: "booleanParserError", + dataParserTransformers: defaultTransformers, + checkerTransformers: [ + ((checker, { buildError }) => (checker as any).kind === "forced-error" + ? buildError() + : E.left("checkerNotSupport", checker)), + ], + typescriptTransformers: tsDefaultTransformers, + }, + ), + ).toThrow(); + }); +}); diff --git a/tests/toDataParser/dataParserTransformer/date.test.ts b/tests/toDataParser/dataParserTransformer/date.test.ts new file mode 100644 index 0000000..feca440 --- /dev/null +++ b/tests/toDataParser/dataParserTransformer/date.test.ts @@ -0,0 +1,55 @@ +import { DPE, E } from "@duplojs/utils"; +import { defaultTransformers, defaultCheckerTransformers, render } from "@scripts/toDataParser"; +import { defaultTransformers as tsDefaultTransformers } from "@scripts/toTypescript"; + +describe("date", () => { + it("renders date parser", () => { + expect( + render( + DPE.date({ coerce: true }), + { + identifier: "dateParser", + dataParserTransformers: defaultTransformers, + checkerTransformers: defaultCheckerTransformers, + typescriptTransformers: tsDefaultTransformers, + }, + ), + ).toMatchSnapshot(); + }); + + it("renders date parser without coerce", () => { + expect( + render( + DPE.date(), + { + identifier: "dateParserNoCoerce", + dataParserTransformers: defaultTransformers, + checkerTransformers: defaultCheckerTransformers, + typescriptTransformers: tsDefaultTransformers, + }, + ), + ).toMatchSnapshot(); + }); + + it("fails when definition checker cannot be rendered", () => { + const schema = DPE.date({ + checkers: [{ kind: "forced-error" } as any], + }); + + expect( + () => render( + schema, + { + identifier: "dateParserError", + dataParserTransformers: defaultTransformers, + checkerTransformers: [ + ((checker, { buildError }) => (checker as any).kind === "forced-error" + ? buildError() + : E.left("checkerNotSupport", checker)), + ], + typescriptTransformers: tsDefaultTransformers, + }, + ), + ).toThrow(); + }); +}); diff --git a/tests/toDataParser/dataParserTransformer/empty.test.ts b/tests/toDataParser/dataParserTransformer/empty.test.ts new file mode 100644 index 0000000..655358b --- /dev/null +++ b/tests/toDataParser/dataParserTransformer/empty.test.ts @@ -0,0 +1,55 @@ +import { DPE, E } from "@duplojs/utils"; +import { defaultTransformers, defaultCheckerTransformers, render } from "@scripts/toDataParser"; +import { defaultTransformers as tsDefaultTransformers } from "@scripts/toTypescript"; + +describe("empty", () => { + it("renders empty parser", () => { + expect( + render( + DPE.empty({ coerce: true }), + { + identifier: "emptyParser", + dataParserTransformers: defaultTransformers, + checkerTransformers: defaultCheckerTransformers, + typescriptTransformers: tsDefaultTransformers, + }, + ), + ).toMatchSnapshot(); + }); + + it("renders empty parser without coerce", () => { + expect( + render( + DPE.empty(), + { + identifier: "emptyParserNoCoerce", + dataParserTransformers: defaultTransformers, + checkerTransformers: defaultCheckerTransformers, + typescriptTransformers: tsDefaultTransformers, + }, + ), + ).toMatchSnapshot(); + }); + + it("fails when definition checker cannot be rendered", () => { + const schema = DPE.empty({ + checkers: [{ kind: "forced-error" } as any], + }); + + expect( + () => render( + schema, + { + identifier: "emptyParserError", + dataParserTransformers: defaultTransformers, + checkerTransformers: [ + ((checker, { buildError }) => (checker as any).kind === "forced-error" + ? buildError() + : E.left("checkerNotSupport", checker)), + ], + typescriptTransformers: tsDefaultTransformers, + }, + ), + ).toThrow(); + }); +}); diff --git a/tests/toDataParser/dataParserTransformer/file.test.ts b/tests/toDataParser/dataParserTransformer/file.test.ts new file mode 100644 index 0000000..00e503a --- /dev/null +++ b/tests/toDataParser/dataParserTransformer/file.test.ts @@ -0,0 +1,76 @@ +import { SDP } from "@duplojs/server-utils"; +import { E } from "@duplojs/utils"; +import { defaultTransformers, defaultCheckerTransformers, render } from "@scripts/toDataParser"; +import { defaultTransformers as tsDefaultTransformers } from "@scripts/toTypescript"; + +describe("file", () => { + it("renders file parser with async constraints", () => { + expect( + render( + SDP.coerce.file({ + checkExist: true, + maxSize: 10, + minSize: 5, + mimeType: /image\/png/, + }), + { + identifier: "fileParser", + dataParserTransformers: defaultTransformers, + checkerTransformers: defaultCheckerTransformers, + typescriptTransformers: tsDefaultTransformers, + }, + ), + ).toMatchSnapshot(); + }); + + it("renders file parser without options", () => { + expect( + render( + SDP.file(), + { + identifier: "fileParserNoOptions", + dataParserTransformers: defaultTransformers, + checkerTransformers: defaultCheckerTransformers, + typescriptTransformers: tsDefaultTransformers, + }, + ), + ).toMatchSnapshot(); + }); + + it("renders extended file parser namespace", () => { + expect( + render( + SDP.file(), + { + identifier: "fileParserExtended", + importMode: "extended", + dataParserTransformers: defaultTransformers, + checkerTransformers: defaultCheckerTransformers, + typescriptTransformers: tsDefaultTransformers, + }, + ), + ).toMatchSnapshot(); + }); + + it("fails when definition checker cannot be rendered", () => { + const schema = SDP.file({}, { + checkers: [{ kind: "forced-error" } as any], + }); + + expect( + () => render( + schema, + { + identifier: "fileParserError", + dataParserTransformers: defaultTransformers, + checkerTransformers: [ + ((checker, { buildError }) => (checker as any).kind === "forced-error" + ? buildError() + : E.left("checkerNotSupport", checker)), + ], + typescriptTransformers: tsDefaultTransformers, + }, + ), + ).toThrow(); + }); +}); diff --git a/tests/toDataParser/dataParserTransformer/lazy.test.ts b/tests/toDataParser/dataParserTransformer/lazy.test.ts new file mode 100644 index 0000000..afdba7e --- /dev/null +++ b/tests/toDataParser/dataParserTransformer/lazy.test.ts @@ -0,0 +1,60 @@ +import { DP, DPE, E } from "@duplojs/utils"; +import { defaultTransformers, defaultCheckerTransformers, render } from "@scripts/toDataParser"; +import { defaultTransformers as tsDefaultTransformers } from "@scripts/toTypescript"; + +describe("lazy", () => { + it("renders lazy parser", () => { + expect( + render( + DPE.lazy(() => DPE.string()), + { + identifier: "lazyParser", + dataParserTransformers: defaultTransformers, + checkerTransformers: defaultCheckerTransformers, + typescriptTransformers: tsDefaultTransformers, + }, + ), + ).toMatchSnapshot(); + }); + + it("fails when lazy inner parser cannot be rendered", () => { + expect( + () => render( + DPE.lazy(() => DPE.string()), + { + identifier: "lazyParserError", + dataParserTransformers: [ + ((dataParser, { buildError }) => DP.stringKind.has(dataParser) + ? buildError() + : E.left("dataParserNotSupport", dataParser)), + ...defaultTransformers, + ], + checkerTransformers: defaultCheckerTransformers, + typescriptTransformers: tsDefaultTransformers, + }, + ), + ).toThrow(); + }); + + it("fails when definition checker cannot be rendered", () => { + const schema = DPE.lazy(() => DPE.string(), { + checkers: [{ kind: "forced-error" } as any], + }); + + expect( + () => render( + schema, + { + identifier: "lazyParserCheckerError", + dataParserTransformers: defaultTransformers, + checkerTransformers: [ + ((checker, { buildError }) => (checker as any).kind === "forced-error" + ? buildError() + : E.left("checkerNotSupport", checker)), + ], + typescriptTransformers: tsDefaultTransformers, + }, + ), + ).toThrow(); + }); +}); diff --git a/tests/toDataParser/dataParserTransformer/literal.test.ts b/tests/toDataParser/dataParserTransformer/literal.test.ts new file mode 100644 index 0000000..356e1d2 --- /dev/null +++ b/tests/toDataParser/dataParserTransformer/literal.test.ts @@ -0,0 +1,41 @@ +import { DPE, E } from "@duplojs/utils"; +import { defaultTransformers, defaultCheckerTransformers, render } from "@scripts/toDataParser"; +import { defaultTransformers as tsDefaultTransformers } from "@scripts/toTypescript"; + +describe("literal", () => { + it("renders literal parser", () => { + expect( + render( + DPE.literal(["foo", 1, 1n, true, false, null, undefined]), + { + identifier: "literalParser", + dataParserTransformers: defaultTransformers, + checkerTransformers: defaultCheckerTransformers, + typescriptTransformers: tsDefaultTransformers, + }, + ), + ).toMatchSnapshot(); + }); + + it("fails when definition checker cannot be rendered", () => { + const schema = DPE.literal(["foo"], { + checkers: [{ kind: "forced-error" } as any], + }); + + expect( + () => render( + schema, + { + identifier: "literalParserCheckerError", + dataParserTransformers: defaultTransformers, + checkerTransformers: [ + ((checker, { buildError }) => (checker as any).kind === "forced-error" + ? buildError() + : E.left("checkerNotSupport", checker)), + ], + typescriptTransformers: tsDefaultTransformers, + }, + ), + ).toThrow(); + }); +}); diff --git a/tests/toDataParser/dataParserTransformer/nil.test.ts b/tests/toDataParser/dataParserTransformer/nil.test.ts new file mode 100644 index 0000000..18cc8e9 --- /dev/null +++ b/tests/toDataParser/dataParserTransformer/nil.test.ts @@ -0,0 +1,53 @@ +import { DPE, E } from "@duplojs/utils"; +import { defaultTransformers, defaultCheckerTransformers, render } from "@scripts/toDataParser"; +import { defaultTransformers as tsDefaultTransformers } from "@scripts/toTypescript"; + +describe("nil", () => { + it("renders nil parser", () => { + expect( + render( + DPE.nil({ coerce: true }), + { + identifier: "nilParser", + dataParserTransformers: defaultTransformers, + checkerTransformers: defaultCheckerTransformers, + typescriptTransformers: tsDefaultTransformers, + }, + ), + ).toMatchSnapshot(); + }); + + it("renders nil parser without coerce", () => { + expect( + render( + DPE.nil(), + { + identifier: "nilParserNoCoerce", + dataParserTransformers: defaultTransformers, + checkerTransformers: defaultCheckerTransformers, + typescriptTransformers: tsDefaultTransformers, + }, + ), + ).toMatchSnapshot(); + }); + + it("fails when definition checker cannot be rendered", () => { + const schema = DPE.nil({ checkers: [{ kind: "forced-error" } as any] }); + + expect( + () => render( + schema, + { + identifier: "nilParserError", + dataParserTransformers: defaultTransformers, + checkerTransformers: [ + ((checker, { buildError }) => (checker as any).kind === "forced-error" + ? buildError() + : E.left("checkerNotSupport", checker)), + ], + typescriptTransformers: tsDefaultTransformers, + }, + ), + ).toThrow(); + }); +}); diff --git a/tests/toDataParser/dataParserTransformer/nullable.test.ts b/tests/toDataParser/dataParserTransformer/nullable.test.ts new file mode 100644 index 0000000..5ebe221 --- /dev/null +++ b/tests/toDataParser/dataParserTransformer/nullable.test.ts @@ -0,0 +1,60 @@ +import { DP, DPE, E } from "@duplojs/utils"; +import { defaultTransformers, defaultCheckerTransformers, render } from "@scripts/toDataParser"; +import { defaultTransformers as tsDefaultTransformers } from "@scripts/toTypescript"; + +describe("nullable", () => { + it("renders nullable parser", () => { + expect( + render( + DPE.nullable(DPE.string()), + { + identifier: "nullableParser", + dataParserTransformers: defaultTransformers, + checkerTransformers: defaultCheckerTransformers, + typescriptTransformers: tsDefaultTransformers, + }, + ), + ).toMatchSnapshot(); + }); + + it("fails when inner parser cannot be rendered", () => { + expect( + () => render( + DPE.nullable(DPE.string()), + { + identifier: "nullableParserError", + dataParserTransformers: [ + ((dataParser, { buildError }) => DP.stringKind.has(dataParser) + ? buildError() + : E.left("dataParserNotSupport", dataParser)), + ...defaultTransformers, + ], + checkerTransformers: defaultCheckerTransformers, + typescriptTransformers: tsDefaultTransformers, + }, + ), + ).toThrow(); + }); + + it("fails when definition checker cannot be rendered", () => { + const schema = DPE.nullable(DPE.string(), { + checkers: [{ kind: "forced-error" } as any], + }); + + expect( + () => render( + schema, + { + identifier: "nullableParserCheckerError", + dataParserTransformers: defaultTransformers, + checkerTransformers: [ + ((checker, { buildError }) => (checker as any).kind === "forced-error" + ? buildError() + : E.left("checkerNotSupport", checker)), + ], + typescriptTransformers: tsDefaultTransformers, + }, + ), + ).toThrow(); + }); +}); diff --git a/tests/toDataParser/dataParserTransformer/number.test.ts b/tests/toDataParser/dataParserTransformer/number.test.ts new file mode 100644 index 0000000..c6fd1f4 --- /dev/null +++ b/tests/toDataParser/dataParserTransformer/number.test.ts @@ -0,0 +1,55 @@ +import { DPE, E } from "@duplojs/utils"; +import { defaultTransformers, defaultCheckerTransformers, render } from "@scripts/toDataParser"; +import { defaultTransformers as tsDefaultTransformers } from "@scripts/toTypescript"; + +describe("number", () => { + it("renders number parser", () => { + expect( + render( + DPE.number({ coerce: true }), + { + identifier: "numberParser", + dataParserTransformers: defaultTransformers, + checkerTransformers: defaultCheckerTransformers, + typescriptTransformers: tsDefaultTransformers, + }, + ), + ).toMatchSnapshot(); + }); + + it("renders number parser without coerce", () => { + expect( + render( + DPE.number(), + { + identifier: "numberParserNoCoerce", + dataParserTransformers: defaultTransformers, + checkerTransformers: defaultCheckerTransformers, + typescriptTransformers: tsDefaultTransformers, + }, + ), + ).toMatchSnapshot(); + }); + + it("fails when definition checker cannot be rendered", () => { + const schema = DPE.number({ + checkers: [{ kind: "forced-error" } as any], + }); + + expect( + () => render( + schema, + { + identifier: "numberParserError", + dataParserTransformers: defaultTransformers, + checkerTransformers: [ + ((checker, { buildError }) => (checker as any).kind === "forced-error" + ? buildError() + : E.left("checkerNotSupport", checker)), + ], + typescriptTransformers: tsDefaultTransformers, + }, + ), + ).toThrow(); + }); +}); diff --git a/tests/toDataParser/dataParserTransformer/object.test.ts b/tests/toDataParser/dataParserTransformer/object.test.ts new file mode 100644 index 0000000..f39406e --- /dev/null +++ b/tests/toDataParser/dataParserTransformer/object.test.ts @@ -0,0 +1,69 @@ +import { DP, DPE, E } from "@duplojs/utils"; +import { defaultTransformers, defaultCheckerTransformers, render } from "@scripts/toDataParser"; +import { defaultTransformers as tsDefaultTransformers } from "@scripts/toTypescript"; + +describe("object", () => { + it("renders object parser", () => { + expect( + render( + DPE.object({ + foo: DPE.string(), + bar: DPE.number(), + }), + { + identifier: "objectParser", + dataParserTransformers: defaultTransformers, + checkerTransformers: defaultCheckerTransformers, + typescriptTransformers: tsDefaultTransformers, + }, + ), + ).toMatchSnapshot(); + }); + + it("fails when a shape property cannot be rendered", () => { + expect( + () => render( + DPE.object({ + foo: DPE.string(), + bar: DPE.number(), + }), + { + identifier: "objectParserError", + dataParserTransformers: [ + ((dataParser, { buildError }) => DP.stringKind.has(dataParser) + ? buildError() + : E.left("dataParserNotSupport", dataParser)), + ...defaultTransformers, + ], + checkerTransformers: defaultCheckerTransformers, + typescriptTransformers: tsDefaultTransformers, + }, + ), + ).toThrow(); + }); + + it("fails when definition checker cannot be rendered", () => { + const schema = DPE.object({ + foo: DPE.string(), + bar: DPE.number(), + }, { + checkers: [{ kind: "forced-error" } as any], + }); + + expect( + () => render( + schema, + { + identifier: "objectParserCheckerError", + dataParserTransformers: defaultTransformers, + checkerTransformers: [ + ((checker, { buildError }) => (checker as any).kind === "forced-error" + ? buildError() + : E.left("checkerNotSupport", checker)), + ], + typescriptTransformers: tsDefaultTransformers, + }, + ), + ).toThrow(); + }); +}); diff --git a/tests/toDataParser/dataParserTransformer/optional.test.ts b/tests/toDataParser/dataParserTransformer/optional.test.ts new file mode 100644 index 0000000..d37b2a1 --- /dev/null +++ b/tests/toDataParser/dataParserTransformer/optional.test.ts @@ -0,0 +1,60 @@ +import { DP, DPE, E } from "@duplojs/utils"; +import { defaultTransformers, defaultCheckerTransformers, render } from "@scripts/toDataParser"; +import { defaultTransformers as tsDefaultTransformers } from "@scripts/toTypescript"; + +describe("optional", () => { + it("renders optional parser", () => { + expect( + render( + DPE.optional(DPE.string()), + { + identifier: "optionalParser", + dataParserTransformers: defaultTransformers, + checkerTransformers: defaultCheckerTransformers, + typescriptTransformers: tsDefaultTransformers, + }, + ), + ).toMatchSnapshot(); + }); + + it("fails when inner parser cannot be rendered", () => { + expect( + () => render( + DPE.optional(DPE.string()), + { + identifier: "optionalParserError", + dataParserTransformers: [ + ((dataParser, { buildError }) => DP.stringKind.has(dataParser) + ? buildError() + : E.left("dataParserNotSupport", dataParser)), + ...defaultTransformers, + ], + checkerTransformers: defaultCheckerTransformers, + typescriptTransformers: tsDefaultTransformers, + }, + ), + ).toThrow(); + }); + + it("fails when definition checker cannot be rendered", () => { + const schema = DPE.optional(DPE.string(), { + checkers: [{ kind: "forced-error" } as any], + }); + + expect( + () => render( + schema, + { + identifier: "optionalParserCheckerError", + dataParserTransformers: defaultTransformers, + checkerTransformers: [ + ((checker, { buildError }) => (checker as any).kind === "forced-error" + ? buildError() + : E.left("checkerNotSupport", checker)), + ], + typescriptTransformers: tsDefaultTransformers, + }, + ), + ).toThrow(); + }); +}); diff --git a/tests/toDataParser/dataParserTransformer/pipe.test.ts b/tests/toDataParser/dataParserTransformer/pipe.test.ts new file mode 100644 index 0000000..126e885 --- /dev/null +++ b/tests/toDataParser/dataParserTransformer/pipe.test.ts @@ -0,0 +1,79 @@ +import { DP, DPE, E } from "@duplojs/utils"; +import { defaultTransformers, defaultCheckerTransformers, render } from "@scripts/toDataParser"; +import { defaultTransformers as tsDefaultTransformers } from "@scripts/toTypescript"; + +describe("pipe", () => { + it("renders pipe parser", () => { + expect( + render( + DPE.pipe(DPE.string(), DPE.number()), + { + identifier: "pipeParser", + dataParserTransformers: defaultTransformers, + checkerTransformers: defaultCheckerTransformers, + typescriptTransformers: tsDefaultTransformers, + }, + ), + ).toMatchSnapshot(); + }); + + it("fails when input parser cannot be rendered", () => { + expect( + () => render( + DPE.pipe(DPE.string(), DPE.number()), + { + identifier: "pipeParserInputError", + dataParserTransformers: [ + ((dataParser, { buildError }) => DP.stringKind.has(dataParser) + ? buildError() + : E.left("dataParserNotSupport", dataParser)), + ...defaultTransformers, + ], + checkerTransformers: defaultCheckerTransformers, + typescriptTransformers: tsDefaultTransformers, + }, + ), + ).toThrow(); + }); + + it("fails when output parser cannot be rendered", () => { + expect( + () => render( + DPE.pipe(DPE.string(), DPE.number()), + { + identifier: "pipeParserOutputError", + dataParserTransformers: [ + ((dataParser, { buildError }) => DP.numberKind.has(dataParser) + ? buildError() + : E.left("dataParserNotSupport", dataParser)), + ...defaultTransformers, + ], + checkerTransformers: defaultCheckerTransformers, + typescriptTransformers: tsDefaultTransformers, + }, + ), + ).toThrow(); + }); + + it("fails when definition checker cannot be rendered", () => { + const schema = DPE.pipe(DPE.string(), DPE.number(), { + checkers: [{ kind: "forced-error" } as any], + }); + + expect( + () => render( + schema, + { + identifier: "pipeParserCheckerError", + dataParserTransformers: defaultTransformers, + checkerTransformers: [ + ((checker, { buildError }) => (checker as any).kind === "forced-error" + ? buildError() + : E.left("checkerNotSupport", checker)), + ], + typescriptTransformers: tsDefaultTransformers, + }, + ), + ).toThrow(); + }); +}); diff --git a/tests/toDataParser/dataParserTransformer/record.test.ts b/tests/toDataParser/dataParserTransformer/record.test.ts new file mode 100644 index 0000000..2017f01 --- /dev/null +++ b/tests/toDataParser/dataParserTransformer/record.test.ts @@ -0,0 +1,83 @@ +import { DP, DPE, E } from "@duplojs/utils"; +import { defaultTransformers, defaultCheckerTransformers, render } from "@scripts/toDataParser"; +import { defaultTransformers as tsDefaultTransformers } from "@scripts/toTypescript"; + +describe("record", () => { + it("renders record parser", () => { + expect( + render( + DPE.record(DPE.string(), DPE.number()), + { + identifier: "recordParser", + dataParserTransformers: defaultTransformers, + checkerTransformers: defaultCheckerTransformers, + typescriptTransformers: tsDefaultTransformers, + }, + ), + ).toMatchSnapshot(); + }); + + it("fails when key parser cannot be rendered", () => { + expect( + () => render( + DPE.record(DPE.string(), DPE.number()), + { + identifier: "recordParserKeyError", + dataParserTransformers: [ + ((dataParser, { buildError }) => DP.stringKind.has(dataParser) + ? buildError() + : E.left("dataParserNotSupport", dataParser)), + ...defaultTransformers, + ], + checkerTransformers: defaultCheckerTransformers, + typescriptTransformers: tsDefaultTransformers, + }, + ), + ).toThrow(); + }); + + it("fails when value parser cannot be rendered", () => { + expect( + () => render( + DPE.record(DPE.string(), DPE.number()), + { + identifier: "recordParserValueError", + dataParserTransformers: [ + ((dataParser, { buildError }) => DP.numberKind.has(dataParser) + ? buildError() + : E.left("dataParserNotSupport", dataParser)), + ...defaultTransformers, + ], + checkerTransformers: defaultCheckerTransformers, + typescriptTransformers: tsDefaultTransformers, + }, + ), + ).toThrow(); + }); + + it("fails when definition checker cannot be rendered", () => { + const schema = DPE.record( + DPE.string(), + DPE.number(), + { + checkers: [{ kind: "forced-error" } as any], + }, + ); + + expect( + () => render( + schema, + { + identifier: "recordParserCheckerError", + dataParserTransformers: defaultTransformers, + checkerTransformers: [ + ((checker, { buildError }) => (checker as any).kind === "forced-error" + ? buildError() + : E.left("checkerNotSupport", checker)), + ], + typescriptTransformers: tsDefaultTransformers, + }, + ), + ).toThrow(); + }); +}); diff --git a/tests/toDataParser/dataParserTransformer/recover.test.ts b/tests/toDataParser/dataParserTransformer/recover.test.ts new file mode 100644 index 0000000..5b613f8 --- /dev/null +++ b/tests/toDataParser/dataParserTransformer/recover.test.ts @@ -0,0 +1,19 @@ +import { render, defaultTransformers, defaultCheckerTransformers } from "@scripts/toDataParser"; +import { defaultTransformers as tsDefaultTransformers } from "@scripts/toTypescript"; +import { DPE } from "@duplojs/utils"; + +describe("recover", () => { + it("renders inner parser", () => { + expect( + render( + DPE.recover(DPE.string(), "fallback"), + { + identifier: "recoverParser", + dataParserTransformers: defaultTransformers, + checkerTransformers: defaultCheckerTransformers, + typescriptTransformers: tsDefaultTransformers, + }, + ), + ).toMatchSnapshot(); + }); +}); diff --git a/tests/toDataParser/dataParserTransformer/string.test.ts b/tests/toDataParser/dataParserTransformer/string.test.ts new file mode 100644 index 0000000..1a608e4 --- /dev/null +++ b/tests/toDataParser/dataParserTransformer/string.test.ts @@ -0,0 +1,41 @@ +import { DPE, E } from "@duplojs/utils"; +import { defaultTransformers, defaultCheckerTransformers, render } from "@scripts/toDataParser"; +import { defaultTransformers as tsDefaultTransformers } from "@scripts/toTypescript"; + +describe("string", () => { + it("renders string parser", () => { + expect( + render( + DPE.string(), + { + identifier: "stringParser", + dataParserTransformers: defaultTransformers, + checkerTransformers: defaultCheckerTransformers, + typescriptTransformers: tsDefaultTransformers, + }, + ), + ).toMatchSnapshot(); + }); + + it("renders string parser when definition has a checker error", () => { + const schema = DPE.string({ + checkers: [{ kind: "forced-error" } as any], + }); + + expect( + () => render( + schema, + { + identifier: "stringParserError", + dataParserTransformers: defaultTransformers, + checkerTransformers: [ + ((checker, { buildError }) => (checker as any).kind === "forced-error" + ? buildError() + : E.left("checkerNotSupport", checker)), + ], + typescriptTransformers: tsDefaultTransformers, + }, + ), + ).toThrow(); + }); +}); diff --git a/tests/toDataParser/dataParserTransformer/templateLiteral.test.ts b/tests/toDataParser/dataParserTransformer/templateLiteral.test.ts new file mode 100644 index 0000000..f020201 --- /dev/null +++ b/tests/toDataParser/dataParserTransformer/templateLiteral.test.ts @@ -0,0 +1,77 @@ +import { DP, DPE, E } from "@duplojs/utils"; +import { defaultTransformers, defaultCheckerTransformers, render } from "@scripts/toDataParser"; +import { defaultTransformers as tsDefaultTransformers } from "@scripts/toTypescript"; + +describe("templateLiteral", () => { + it("renders template literal parser", () => { + expect( + render( + DPE.templateLiteral([ + "pre-", + 1n, + "mid-", + true, + "-false-", + false, + "-str-", + "abc", + "-num-", + 42, + "-null-", + null, + "-undef-", + undefined, + "-dp-", + DPE.string(), + ]), + { + identifier: "templateLiteralParser", + dataParserTransformers: defaultTransformers, + checkerTransformers: defaultCheckerTransformers, + typescriptTransformers: tsDefaultTransformers, + }, + ), + ).toMatchSnapshot(); + }); + + it("fails when nested parser cannot be rendered", () => { + expect( + () => render( + DPE.templateLiteral(["user-", DPE.number(), "-id"]), + { + identifier: "templateLiteralParserError", + dataParserTransformers: [ + ((dataParser, { buildError }) => DP.numberKind.has(dataParser) + ? buildError() + : E.left("dataParserNotSupport", dataParser)), + ...defaultTransformers, + ], + checkerTransformers: defaultCheckerTransformers, + typescriptTransformers: tsDefaultTransformers, + }, + ), + ).toThrow(); + }); + + it("fails when definition checker cannot be rendered", () => { + const schema = DPE.templateLiteral(["user-", DPE.number(), "-id"], { + checkers: [{ kind: "forced-error" } as any], + }); + + expect( + () => render( + schema, + { + identifier: "templateLiteralParserCheckerError", + dataParserTransformers: defaultTransformers, + checkerTransformers: [ + ((checker, { buildError }) => (checker as any).kind === "forced-error" + ? buildError() + : E.left("checkerNotSupport", checker)), + ], + typescriptTransformers: tsDefaultTransformers, + }, + ), + ).toThrow(); + }); +}); diff --git a/tests/toDataParser/dataParserTransformer/time.test.ts b/tests/toDataParser/dataParserTransformer/time.test.ts new file mode 100644 index 0000000..58223d8 --- /dev/null +++ b/tests/toDataParser/dataParserTransformer/time.test.ts @@ -0,0 +1,57 @@ +import { DDate, DP, DPE, E } from "@duplojs/utils"; +import { defaultTransformers, defaultCheckerTransformers, render } from "@scripts/toDataParser"; +import { defaultTransformers as tsDefaultTransformers } from "@scripts/toTypescript"; + +describe("time", () => { + it("renders time parser with checker imports", () => { + expect( + render( + DPE.time({ coerce: true }).addChecker( + DP.checkerTimeMin(DDate.createTime(1, "millisecond")), + ), + { + identifier: "timeParser", + dataParserTransformers: defaultTransformers, + checkerTransformers: defaultCheckerTransformers, + typescriptTransformers: tsDefaultTransformers, + }, + ), + ).toMatchSnapshot(); + }); + + it("renders time parser without coerce and without checkers", () => { + expect( + render( + DPE.time(), + { + identifier: "timeParserNoCoerce", + dataParserTransformers: defaultTransformers, + checkerTransformers: defaultCheckerTransformers, + typescriptTransformers: tsDefaultTransformers, + }, + ), + ).toMatchSnapshot(); + }); + + it("fails when definition checker cannot be rendered", () => { + const schema = DPE.time({ + checkers: [{ kind: "forced-error" } as any], + }); + + expect( + () => render( + schema, + { + identifier: "timeParserError", + dataParserTransformers: defaultTransformers, + checkerTransformers: [ + ((checker, { buildError }) => (checker as any).kind === "forced-error" + ? buildError() + : E.left("checkerNotSupport", checker)), + ], + typescriptTransformers: tsDefaultTransformers, + }, + ), + ).toThrow(); + }); +}); diff --git a/tests/toDataParser/dataParserTransformer/transform.test.ts b/tests/toDataParser/dataParserTransformer/transform.test.ts new file mode 100644 index 0000000..71b72f7 --- /dev/null +++ b/tests/toDataParser/dataParserTransformer/transform.test.ts @@ -0,0 +1,166 @@ +/* eslint-disable @typescript-eslint/consistent-type-imports */ +import { DP, DPE, E } from "@duplojs/utils"; +import { defaultTransformers, defaultCheckerTransformers, render } from "@scripts/toDataParser"; +import { defaultTransformers as tsDefaultTransformers } from "@scripts/toTypescript"; +import { afterEach, vi } from "vitest"; +import * as ts from "typescript"; + +vi.mock("typescript", async() => { + const actual = await vi.importActual("typescript"); + + return { + ...actual, + createSourceFile: vi.fn(actual.createSourceFile), + isExpressionStatement: vi.fn(actual.isExpressionStatement), + isParenthesizedExpression: vi.fn(actual.isParenthesizedExpression), + }; +}); + +afterEach(() => { + vi.restoreAllMocks(); +}); + +describe("transform", () => { + it("renders transform parser", () => { + expect( + render( + DPE.transform(DPE.string(), (value) => value.trim()), + { + identifier: "transformParser", + dataParserTransformers: defaultTransformers, + checkerTransformers: defaultCheckerTransformers, + typescriptTransformers: tsDefaultTransformers, + }, + ), + ).toMatchSnapshot(); + }); + + it("fails when inner parser cannot be rendered", () => { + expect( + () => render( + DPE.transform(DPE.string(), (value) => value.trim()), + { + identifier: "transformParserInnerError", + dataParserTransformers: [ + ((dataParser, { buildError }) => DP.stringKind.has(dataParser) + ? buildError() + : E.left("dataParserNotSupport", dataParser)), + ...defaultTransformers, + ], + checkerTransformers: defaultCheckerTransformers, + typescriptTransformers: tsDefaultTransformers, + }, + ), + ).toThrow(); + }); + + it("fails on native functions", () => { + expect( + () => render( + DPE.transform(DPE.string(), Math.max as any), + { + identifier: "transformParserNativeError", + dataParserTransformers: defaultTransformers, + checkerTransformers: defaultCheckerTransformers, + typescriptTransformers: tsDefaultTransformers, + }, + ), + ).toThrow(); + }); + + it("fails on malformed functions", () => { + const schema = DPE.transform(DPE.string(), { toString: () => "foo" } as any); + expect( + () => render( + schema, + { + identifier: "transformParserMalformedError", + dataParserTransformers: defaultTransformers, + checkerTransformers: defaultCheckerTransformers, + typescriptTransformers: tsDefaultTransformers, + }, + ), + ).toThrow(); + }); + + it("fails when the function source cannot be parsed as an expression", () => { + const schema = DPE.transform(DPE.string(), { toString: () => "" } as any); + + expect( + () => render( + schema, + { + identifier: "transformParserEmptySourceError", + dataParserTransformers: defaultTransformers, + checkerTransformers: defaultCheckerTransformers, + typescriptTransformers: tsDefaultTransformers, + }, + ), + ).toThrow(); + }); + + it("fails when the parsed statement is not an expression statement", () => { + vi.mocked(ts.createSourceFile).mockReturnValue({ + statements: [{ kind: ts.SyntaxKind.VariableStatement }], + } as any); + vi.mocked(ts.isExpressionStatement).mockReturnValue(false); + + expect( + () => render( + DPE.transform(DPE.string(), (value) => value.trim()), + { + identifier: "transformParserStatementError", + dataParserTransformers: defaultTransformers, + checkerTransformers: defaultCheckerTransformers, + typescriptTransformers: tsDefaultTransformers, + }, + ), + ).toThrow(); + }); + + it("fails when the parsed function is not parenthesized", () => { + vi.mocked(ts.createSourceFile).mockReturnValue({ + statements: [ + { + expression: {}, + }, + ], + } as any); + vi.mocked(ts.isExpressionStatement).mockReturnValue(true); + vi.mocked(ts.isParenthesizedExpression).mockReturnValue(false); + + expect( + () => render( + DPE.transform(DPE.string(), (value) => value.trim()), + { + identifier: "transformParserParenthesizedError", + dataParserTransformers: defaultTransformers, + checkerTransformers: defaultCheckerTransformers, + typescriptTransformers: tsDefaultTransformers, + }, + ), + ).toThrow(); + }); + + it("fails when definition checker cannot be rendered", () => { + const schema = DPE.transform(DPE.string(), (value) => value.trim(), { + checkers: [{ kind: "forced-error" } as any], + }); + + expect( + () => render( + schema, + { + identifier: "transformParserCheckerError", + dataParserTransformers: defaultTransformers, + checkerTransformers: [ + ((checker, { buildError }) => (checker as any).kind === "forced-error" + ? buildError() + : E.left("checkerNotSupport", checker)), + ], + typescriptTransformers: tsDefaultTransformers, + }, + ), + ).toThrow(); + }); +}); diff --git a/tests/toDataParser/dataParserTransformer/tuple.test.ts b/tests/toDataParser/dataParserTransformer/tuple.test.ts new file mode 100644 index 0000000..55795ed --- /dev/null +++ b/tests/toDataParser/dataParserTransformer/tuple.test.ts @@ -0,0 +1,94 @@ +import { DP, DPE, E } from "@duplojs/utils"; +import { defaultTransformers, defaultCheckerTransformers, render } from "@scripts/toDataParser"; +import { defaultTransformers as tsDefaultTransformers } from "@scripts/toTypescript"; + +describe("tuple", () => { + it("renders tuple parser with rest", () => { + expect( + render( + DPE.tuple([DPE.string()], { rest: DPE.number() }), + { + identifier: "tupleParser", + dataParserTransformers: defaultTransformers, + checkerTransformers: defaultCheckerTransformers, + typescriptTransformers: tsDefaultTransformers, + }, + ), + ).toMatchSnapshot(); + }); + + it("renders tuple parser without rest", () => { + expect( + render( + DPE.tuple([DPE.string()]), + { + identifier: "tupleParserNoRest", + dataParserTransformers: defaultTransformers, + checkerTransformers: defaultCheckerTransformers, + typescriptTransformers: tsDefaultTransformers, + }, + ), + ).toMatchSnapshot(); + }); + + it("fails when tuple shape cannot be rendered", () => { + expect( + () => render( + DPE.tuple([DPE.string()], { rest: DPE.number() }), + { + identifier: "tupleParserShapeError", + dataParserTransformers: [ + ((dataParser, { buildError }) => DP.stringKind.has(dataParser) + ? buildError() + : E.left("dataParserNotSupport", dataParser)), + ...defaultTransformers, + ], + checkerTransformers: defaultCheckerTransformers, + typescriptTransformers: tsDefaultTransformers, + }, + ), + ).toThrow(); + }); + + it("fails when tuple rest cannot be rendered", () => { + expect( + () => render( + DPE.tuple([DPE.string()], { rest: DPE.number() }), + { + identifier: "tupleParserRestError", + dataParserTransformers: [ + ((dataParser, { buildError }) => DP.numberKind.has(dataParser) + ? buildError() + : E.left("dataParserNotSupport", dataParser)), + ...defaultTransformers, + ], + checkerTransformers: defaultCheckerTransformers, + typescriptTransformers: tsDefaultTransformers, + }, + ), + ).toThrow(); + }); + + it("fails when definition checker cannot be rendered", () => { + const schema = DPE.tuple([DPE.string()], { + rest: DPE.number(), + checkers: [{ kind: "forced-error" } as any], + }); + + expect( + () => render( + schema, + { + identifier: "tupleParserCheckerError", + dataParserTransformers: defaultTransformers, + checkerTransformers: [ + ((checker, { buildError }) => (checker as any).kind === "forced-error" + ? buildError() + : E.left("checkerNotSupport", checker)), + ], + typescriptTransformers: tsDefaultTransformers, + }, + ), + ).toThrow(); + }); +}); diff --git a/tests/toDataParser/dataParserTransformer/union.test.ts b/tests/toDataParser/dataParserTransformer/union.test.ts new file mode 100644 index 0000000..b24f08b --- /dev/null +++ b/tests/toDataParser/dataParserTransformer/union.test.ts @@ -0,0 +1,60 @@ +import { DP, DPE, E } from "@duplojs/utils"; +import { defaultTransformers, defaultCheckerTransformers, render } from "@scripts/toDataParser"; +import { defaultTransformers as tsDefaultTransformers } from "@scripts/toTypescript"; + +describe("union", () => { + it("renders union parser", () => { + expect( + render( + DPE.union([DPE.string(), DPE.number()]), + { + identifier: "unionParser", + dataParserTransformers: defaultTransformers, + checkerTransformers: defaultCheckerTransformers, + typescriptTransformers: tsDefaultTransformers, + }, + ), + ).toMatchSnapshot(); + }); + + it("fails when option parser cannot be rendered", () => { + expect( + () => render( + DPE.union([DPE.string(), DPE.number()]), + { + identifier: "unionParserOptionError", + dataParserTransformers: [ + ((dataParser, { buildError }) => DP.stringKind.has(dataParser) + ? buildError() + : E.left("dataParserNotSupport", dataParser)), + ...defaultTransformers, + ], + checkerTransformers: defaultCheckerTransformers, + typescriptTransformers: tsDefaultTransformers, + }, + ), + ).toThrow(); + }); + + it("fails when definition checker cannot be rendered", () => { + const schema = DPE.union([DPE.string(), DPE.number()], { + checkers: [{ kind: "forced-error" } as any], + }); + + expect( + () => render( + schema, + { + identifier: "unionParserCheckerError", + dataParserTransformers: defaultTransformers, + checkerTransformers: [ + ((checker, { buildError }) => (checker as any).kind === "forced-error" + ? buildError() + : E.left("checkerNotSupport", checker)), + ], + typescriptTransformers: tsDefaultTransformers, + }, + ), + ).toThrow(); + }); +}); diff --git a/tests/toDataParser/dataParserTransformer/unknown.test.ts b/tests/toDataParser/dataParserTransformer/unknown.test.ts new file mode 100644 index 0000000..61ff098 --- /dev/null +++ b/tests/toDataParser/dataParserTransformer/unknown.test.ts @@ -0,0 +1,41 @@ +import { DPE, E } from "@duplojs/utils"; +import { defaultTransformers, defaultCheckerTransformers, render } from "@scripts/toDataParser"; +import { defaultTransformers as tsDefaultTransformers } from "@scripts/toTypescript"; + +describe("unknown", () => { + it("renders unknown parser", () => { + expect( + render( + DPE.unknown(), + { + identifier: "unknownParser", + dataParserTransformers: defaultTransformers, + checkerTransformers: defaultCheckerTransformers, + typescriptTransformers: tsDefaultTransformers, + }, + ), + ).toMatchSnapshot(); + }); + + it("fails when definition checker cannot be rendered", () => { + const schema = DPE.unknown({ + checkers: [{ kind: "forced-error" } as any], + }); + + expect( + () => render( + schema, + { + identifier: "unknownParserError", + dataParserTransformers: defaultTransformers, + checkerTransformers: [ + ((checker, { buildError }) => (checker as any).kind === "forced-error" + ? buildError() + : E.left("checkerNotSupport", checker)), + ], + typescriptTransformers: tsDefaultTransformers, + }, + ), + ).toThrow(); + }); +}); diff --git a/tests/toDataParser/dependenciesContext.test.ts b/tests/toDataParser/dependenciesContext.test.ts new file mode 100644 index 0000000..79454aa --- /dev/null +++ b/tests/toDataParser/dependenciesContext.test.ts @@ -0,0 +1,151 @@ +import { DP, E } from "@duplojs/utils"; +import { defaultTransformers as tsDefaultTransformers } from "@scripts/toTypescript"; +import { defaultCheckerTransformers, defaultTransformers, buildContext, printer } from "@scripts/toDataParser"; + +describe("dependencies context", () => { + it("dataParser with no identifier", () => { + const dataParser = DP.object({ + name: DP.string(), + age: DP.number(), + }); + + const result = E.unwrapRightOrThrow( + buildContext( + dataParser, + { + identifier: "test1", + dataParserTransformers: defaultTransformers, + checkerTransformers: defaultCheckerTransformers, + typescriptTransformers: tsDefaultTransformers, + }, + ), + ); + + expect([...result.context.entries()]).toStrictEqual([ + [ + expect.objectContaining(DP.emptyKind.setTo({})), + expect.objectContaining({ + dependencies: new Set(), + }), + ], + ]); + + expect(printer(result)).toMatchSnapshot(); + }); + + it("dataParser with top level identifier but it doesn't match with builder identifier", () => { + const dataParser = DP.object({ + name: DP.string(), + age: DP.number(), + }).addIdentifier("otherTest"); + + const result = E.unwrapRightOrThrow( + buildContext( + dataParser, + { + identifier: "test2", + dataParserTransformers: defaultTransformers, + checkerTransformers: defaultCheckerTransformers, + typescriptTransformers: tsDefaultTransformers, + }, + ), + ); + + expect([...result.context.entries()]).toStrictEqual([ + [ + dataParser, + expect.objectContaining({ + dependencies: new Set(), + }), + ], + [ + expect.objectContaining(DP.emptyKind.setTo({})), + expect.objectContaining({ + dependencies: new Set([dataParser]), + }), + ], + ]); + + expect(printer(result)).toMatchSnapshot(); + }); + + it("dataParser with top level identifier", () => { + const dataParser = DP.object({ + name: DP.string(), + age: DP.number(), + }).addIdentifier("test3"); + + const result = E.unwrapRightOrThrow( + buildContext( + dataParser, + { + identifier: "test3", + dataParserTransformers: defaultTransformers, + checkerTransformers: defaultCheckerTransformers, + typescriptTransformers: tsDefaultTransformers, + }, + ), + ); + + expect([...result.context.entries()]).toStrictEqual([ + [ + dataParser, + expect.objectContaining({ + dependencies: new Set([]), + }), + ], + ]); + + expect(printer(result)).toMatchSnapshot(); + }); + + it("dataParser with sub dataParser with identifier", () => { + const userNameDataParser = DP.string().addIdentifier("UserName"); + const userAgeDataParser = DP.number().addIdentifier("UserAge"); + const dataParser = DP.object({ + name: userNameDataParser, + age: userAgeDataParser, + }).addIdentifier("otherTest"); + + const result = E.unwrapRightOrThrow( + buildContext( + dataParser, + { + identifier: "test4", + dataParserTransformers: defaultTransformers, + checkerTransformers: defaultCheckerTransformers, + typescriptTransformers: tsDefaultTransformers, + }, + ), + ); + + expect([...result.context.entries()]).toStrictEqual([ + [ + userNameDataParser, + expect.objectContaining({ + dependencies: new Set([]), + }), + ], + [ + userAgeDataParser, + expect.objectContaining({ + dependencies: new Set([]), + }), + ], + [ + dataParser, + expect.objectContaining({ + dependencies: new Set([userNameDataParser, userAgeDataParser]), + }), + ], + [ + expect.objectContaining(DP.emptyKind.setTo({})), + expect.objectContaining({ + dependencies: new Set([dataParser]), + }), + ], + ]); + + expect(printer(result)).toMatchSnapshot(); + }); +}); diff --git a/tests/toDataParser/override.test.ts b/tests/toDataParser/override.test.ts new file mode 100644 index 0000000..b61f21d --- /dev/null +++ b/tests/toDataParser/override.test.ts @@ -0,0 +1,108 @@ +import "@scripts/toDataParser/override"; +import { DP, DPE, justReturn } from "@duplojs/utils"; +import { factory } from "typescript"; +import { defaultCheckerTransformers, defaultTransformers, render } from "@scripts/toDataParser"; +import { defaultTransformers as tsDefaultTransformers } from "@scripts/toTypescript"; +import { type TransformerBuildFunction } from "@scripts/toDataParser/dataParserTransformer"; + +describe("override", () => { + it("setIdentifier", () => { + const schema = DP.string(); + + expect(schema.definition.identifier).toBe(undefined); + + schema.setIdentifier("test"); + + expect(schema.definition.identifier).toBe("test"); + }); + + it("addIdentifier", () => { + const schema = DP.string(); + + expect(schema.definition.identifier).toBe(undefined); + + const newSchema = schema.addIdentifier("test"); + + expect(schema.definition.identifier).toBe(undefined); + expect(newSchema.definition.identifier).toBe("test"); + + const newSchemaWithChecker = newSchema.addChecker(DP.checkerRefine(justReturn(true))); + + schema.setIdentifier("test1"); + + expect(newSchemaWithChecker.definition.identifier).toBe("test"); + }); + + it("setOverrideDataParserTransformer", () => { + const schema = DP.string(); + + expect(schema.definition.overrideDataParserTransformer).toBe(undefined); + + schema.setOverrideDataParserTransformer( + factory.createCallExpression( + factory.createIdentifier("test"), + undefined, + [], + ), + ); + + expect(typeof schema.definition.overrideDataParserTransformer).toBe("function"); + }); + + it("addOverrideDataParserTransformer", () => { + const schema = DP.string(); + const overrideTransformer: TransformerBuildFunction = (__, { success }) => success( + factory.createCallExpression( + factory.createIdentifier("test"), + undefined, + [], + ), + ); + + expect(schema.definition.overrideDataParserTransformer).toBe(undefined); + + const newSchema = schema.addOverrideDataParserTransformer(overrideTransformer); + + expect(schema.definition.overrideDataParserTransformer).toBe(undefined); + expect(newSchema.definition.overrideDataParserTransformer).toBe(overrideTransformer); + + const newSchemaWithChecker = newSchema.addChecker(DP.checkerRefine(justReturn(true))); + + schema.setOverrideDataParserTransformer( + factory.createCallExpression( + factory.createIdentifier("test1"), + undefined, + [], + ), + ); + + expect(newSchemaWithChecker.definition.overrideDataParserTransformer).toBe(overrideTransformer); + }); + + it("uses overrideDataParserTransformer in render", () => { + const schema = DPE.string().addOverrideDataParserTransformer( + (__, { success, dependencyIdentifier }) => success( + factory.createCallExpression( + factory.createPropertyAccessExpression( + dependencyIdentifier, + factory.createIdentifier("number"), + ), + undefined, + [], + ), + ), + ); + + const result = render( + schema, + { + identifier: "override", + dataParserTransformers: defaultTransformers, + checkerTransformers: defaultCheckerTransformers, + typescriptTransformers: tsDefaultTransformers, + }, + ); + + expect(result).toContain("export const overrideDataParser = DP.number();"); + }); +}); diff --git a/tests/toDataParser/render.test.ts b/tests/toDataParser/render.test.ts new file mode 100644 index 0000000..297cc95 --- /dev/null +++ b/tests/toDataParser/render.test.ts @@ -0,0 +1,216 @@ +import { DDate, DP, DPE } from "@duplojs/utils"; +import { defaultTransformers as tsDefaultTransformers } from "@scripts/toTypescript"; +import { defaultCheckerTransformers, defaultTransformers, render } from "@scripts/toDataParser"; + +describe("render", () => { + it("renders anonymous recursive dataParser with a temporary const", () => { + interface RecursiveNode { + next: RecursiveNode; + } + + const schema: DPE.DataParser = DPE.object({ + next: DPE.lazy(() => schema), + }).contract(); + + expect( + render( + schema, + { + identifier: "recursiveNodeParser", + dataParserTransformers: defaultTransformers, + checkerTransformers: defaultCheckerTransformers, + typescriptTransformers: tsDefaultTransformers, + }, + ), + ).toMatchSnapshot(); + }); + + it("renders named dependencies before their consumers", () => { + const child = DPE.string().addIdentifier("childParser"); + const schema = DPE.object({ child }); + + expect( + render( + schema, + { + identifier: "parentParser", + dataParserTransformers: defaultTransformers, + checkerTransformers: defaultCheckerTransformers, + typescriptTransformers: tsDefaultTransformers, + }, + ), + ).toMatchSnapshot(); + }); + + it("renders complex nested recursive dataParsers", () => { + type RecursiveArray = (RecursiveArray | RecursiveObject | string)[]; + + type RecursiveTuple = [ + string, + RecursiveObject, + (RecursiveTuple | RecursiveUnion | string)[], + ]; + + type RecursiveUnion = + | string + | RecursiveObject + | RecursiveTuple + | RecursiveUnion[]; + + interface RecursiveObject { + recursiveProp: { + self: RecursiveObject; + array: RecursiveArray; + tuple: RecursiveTuple; + union: RecursiveUnion; + }; + children: RecursiveObject[]; + tuple: RecursiveTuple; + array: RecursiveArray; + union: RecursiveUnion; + } + + const schemaArray: DPE.DataParser = DPE.array( + DPE.union([ + DPE.string(), + DPE.lazy(() => schemaArray), + DPE.lazy(() => schema), + ]), + ).contract(); + + const schemaTuple: DPE.DataParser = DPE.tuple([ + DPE.string(), + DPE.lazy(() => schema), + DPE.union([ + DPE.lazy(() => schemaTuple), + DPE.lazy(() => schemaUnion), + DPE.string(), + ]).array(), + ]).contract(); + + const schemaUnion: DPE.DataParser = DPE.union([ + DPE.string(), + DPE.lazy(() => schema), + DPE.lazy(() => schemaTuple), + DPE.array(DPE.lazy(() => schemaUnion)), + ]).contract(); + + const schema: DPE.DataParser = DPE.object({ + recursiveProp: DPE.object({ + self: DPE.lazy(() => schema), + array: schemaArray, + tuple: schemaTuple, + union: schemaUnion, + }), + children: DPE.array(DPE.lazy(() => schema)), + tuple: schemaTuple, + array: schemaArray, + union: schemaUnion, + }).contract(); + + expect( + render( + schema, + { + identifier: "complexRecursiveParser", + dataParserTransformers: defaultTransformers, + checkerTransformers: defaultCheckerTransformers, + typescriptTransformers: tsDefaultTransformers, + }, + ), + ).toMatchSnapshot(); + }); + + it("renders checked dataParsers in normal and extended modes", () => { + const checkedSchema = DP.object({ + startAt: DP.time().addChecker( + DP.checkerTimeMin(DDate.createTime(1000, "millisecond")), + DP.checkerTimeMax(DDate.createTime(5000, "millisecond")), + ), + name: DP.string().addChecker(DP.checkerStringMin(2)), + count: DP.number().addChecker( + DP.checkerInt(), + DP.checkerNumberMin(1), + ), + }); + const checkedExtendedSchema = DPE.object({ + startAt: DPE + .time() + .min(DDate.createTime(250, "millisecond")), + tags: DPE + .string() + .min(1) + .array() + .min(1), + }); + + expect( + render( + checkedSchema, + { + identifier: "checkedParser", + dataParserTransformers: defaultTransformers, + checkerTransformers: defaultCheckerTransformers, + typescriptTransformers: tsDefaultTransformers, + }, + ), + ).toMatchSnapshot(); + + expect( + render( + checkedExtendedSchema, + { + identifier: "checkedExtendedParser", + importMode: "extended", + dataParserTransformers: defaultTransformers, + checkerTransformers: defaultCheckerTransformers, + typescriptTransformers: tsDefaultTransformers, + }, + ), + ).toMatchSnapshot(); + }); + + it("renders dataParser with nested checkers", () => { + const compactSchema = DPE.object({ + name: DPE.string().min(2), + roles: DPE.literal(["admin", "editor"]).array().min(1), + contact: DPE.union([ + DPE.object({ + email: DPE.email(), + }), + DPE.object({ + phone: DPE.string().regex(/^[+\d][\d\s-]{5,}$/), + }), + ]), + }); + + expect( + render( + compactSchema, + { + identifier: "compactParser", + dataParserTransformers: defaultTransformers, + checkerTransformers: defaultCheckerTransformers, + typescriptTransformers: tsDefaultTransformers, + }, + ), + ).toMatchSnapshot(); + }); + + it("normalizes the render identifier into a dataParser const identifier", () => { + const identifier = "UserParser"; + const result = render( + DPE.string(), + { + identifier, + dataParserTransformers: defaultTransformers, + checkerTransformers: defaultCheckerTransformers, + typescriptTransformers: tsDefaultTransformers, + }, + ); + const renderedIdentifier = result.match(/export const (?\w+)/)?.groups?.identifier; + + expect(identifier).toBe("UserParser"); + expect(renderedIdentifier).toBe("userParserDataParser"); + }); +}); diff --git a/tests/toTypescript/__snapshots__/recursive.test.ts.snap b/tests/toTypescript/__snapshots__/recursive.test.ts.snap index 699f58b..5f3fde3 100644 --- a/tests/toTypescript/__snapshots__/recursive.test.ts.snap +++ b/tests/toTypescript/__snapshots__/recursive.test.ts.snap @@ -1,7 +1,7 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html exports[`recursive > when using an schema two time but non recursive 1`] = ` -"export type noneRecursive = { +"export type NoneRecursive = { one: { prop: string; }; diff --git a/tests/toTypescript/render.test.ts b/tests/toTypescript/render.test.ts new file mode 100644 index 0000000..55d3682 --- /dev/null +++ b/tests/toTypescript/render.test.ts @@ -0,0 +1,20 @@ +import { DPE } from "@duplojs/utils"; +import { defaultTransformers, render } from "@scripts/toTypescript"; + +describe("render", () => { + it("normalizes the render identifier into a type identifier", () => { + const identifier = "userType"; + const result = render( + DPE.string(), + { + identifier, + transformers: defaultTransformers, + mode: "out", + }, + ); + const renderedIdentifier = result.match(/export type (?\w+)/)?.groups?.identifier; + + expect(identifier).toBe("userType"); + expect(renderedIdentifier).toBe("UserType"); + }); +}); diff --git a/tests/toTypescript/transfomers/__snapshots__/date.test.ts.snap b/tests/toTypescript/transfomers/__snapshots__/date.test.ts.snap index fbdb74f..773752a 100644 --- a/tests/toTypescript/transfomers/__snapshots__/date.test.ts.snap +++ b/tests/toTypescript/transfomers/__snapshots__/date.test.ts.snap @@ -1,31 +1,31 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html exports[`date > mode in 1`] = ` -"import type { TheDate, SerializedTheDate } from "@duplojs/utils/date"; +"import { TheDate, SerializedTheDate } from "@duplojs/utils/date"; export type Date = TheDate | Date | SerializedTheDate;" `; exports[`date > mode in 2`] = ` -"import type { TheDate, SerializedTheDate } from "@duplojs/utils/date"; +"import { TheDate, SerializedTheDate } from "@duplojs/utils/date"; export type DateIdentifier = TheDate | Date | SerializedTheDate;" `; exports[`date > mode out 1`] = ` -"import type { TheDate } from "@duplojs/utils/date"; +"import { TheDate } from "@duplojs/utils/date"; export type Date = TheDate;" `; exports[`date > mode out 2`] = ` -"import type { TheDate } from "@duplojs/utils/date"; +"import { TheDate } from "@duplojs/utils/date"; export type DateIdentifier = TheDate;" `; -exports[`date > with preset importType 1`] = ` -"import type { TheDate, SerializedTheDate } from "@duplojs/utils/date"; +exports[`date > with preset importContext 1`] = ` +"import { TheDate, SerializedTheDate } from "@duplojs/utils/date"; export type Date = TheDate | Date | SerializedTheDate;" `; diff --git a/tests/toTypescript/transfomers/__snapshots__/file.test.ts.snap b/tests/toTypescript/transfomers/__snapshots__/file.test.ts.snap index 0e0facf..93a5830 100644 --- a/tests/toTypescript/transfomers/__snapshots__/file.test.ts.snap +++ b/tests/toTypescript/transfomers/__snapshots__/file.test.ts.snap @@ -1,19 +1,13 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html exports[`file > basic 1`] = ` -"import type { FileInterface } from "@duplojs/server-utils/file"; +"import { FileInterface } from "@duplojs/server-utils/file"; export type File = FileInterface;" `; exports[`file > basic 2`] = ` -"import type { FileInterface } from "@duplojs/server-utils/file"; +"import { FileInterface } from "@duplojs/server-utils/file"; export type FileIdentifier = FileInterface;" `; - -exports[`file > with preset importType 1`] = ` -"import type { FileInterface } from "@duplojs/server-utils/file"; - -export type File = FileInterface;" -`; diff --git a/tests/toTypescript/transfomers/__snapshots__/time.test.ts.snap b/tests/toTypescript/transfomers/__snapshots__/time.test.ts.snap index 316a410..bbf1d5a 100644 --- a/tests/toTypescript/transfomers/__snapshots__/time.test.ts.snap +++ b/tests/toTypescript/transfomers/__snapshots__/time.test.ts.snap @@ -1,31 +1,31 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html exports[`time > mode in 1`] = ` -"import type { TheTime, SerializedTheTime } from "@duplojs/utils/date"; +"import { TheTime, SerializedTheTime } from "@duplojs/utils/date"; export type Time = SerializedTheTime | number | TheTime;" `; exports[`time > mode in 2`] = ` -"import type { TheTime, SerializedTheTime } from "@duplojs/utils/date"; +"import { TheTime, SerializedTheTime } from "@duplojs/utils/date"; export type TimeIdentifier = SerializedTheTime | number | TheTime;" `; exports[`time > mode out 1`] = ` -"import type { TheTime } from "@duplojs/utils/date"; +"import { TheTime } from "@duplojs/utils/date"; export type Time = TheTime;" `; exports[`time > mode out 2`] = ` -"import type { TheTime } from "@duplojs/utils/date"; +"import { TheTime } from "@duplojs/utils/date"; export type TimeIdentifier = TheTime;" `; -exports[`time > with preset importType 1`] = ` -"import type { TheTime, SerializedTheTime } from "@duplojs/utils/date"; +exports[`time > with preset importContext 1`] = ` +"import { TheTime, SerializedTheTime } from "@duplojs/utils/date"; export type Time = SerializedTheTime | number | TheTime;" `; diff --git a/tests/toTypescript/transfomers/date.test.ts b/tests/toTypescript/transfomers/date.test.ts index 966b220..a2649e5 100644 --- a/tests/toTypescript/transfomers/date.test.ts +++ b/tests/toTypescript/transfomers/date.test.ts @@ -1,5 +1,5 @@ import { DPE } from "@duplojs/utils"; -import { defaultTransformers, type MapImportType, render } from "@scripts/toTypescript"; +import { defaultTransformers, type MapImportContext, render } from "@scripts/toTypescript"; describe("date", () => { it("mode out", () => { @@ -54,9 +54,9 @@ describe("date", () => { ).toMatchSnapshot(); }); - it("with preset importType", () => { - const importType: MapImportType = new Map(); - importType.set("@duplojs/utils/date", ["TheDate"]); + it("with preset importContext", () => { + const importContext: MapImportContext = new Map(); + importContext.set("@duplojs/utils/date", { direct: ["TheDate"] }); expect( render( @@ -65,7 +65,7 @@ describe("date", () => { identifier: "Date", transformers: defaultTransformers, mode: "in", - importType, + importContext, }, ), ).toMatchSnapshot(); diff --git a/tests/toTypescript/transfomers/file.test.ts b/tests/toTypescript/transfomers/file.test.ts index 8c056d7..2275548 100644 --- a/tests/toTypescript/transfomers/file.test.ts +++ b/tests/toTypescript/transfomers/file.test.ts @@ -1,5 +1,5 @@ import { SDP } from "@duplojs/server-utils"; -import { defaultTransformers, type MapImportType, render } from "@scripts/toTypescript"; +import { defaultTransformers, type MapImportContext, render } from "@scripts/toTypescript"; describe("file", () => { it("basic", () => { @@ -27,21 +27,4 @@ describe("file", () => { ), ).toMatchSnapshot(); }); - - it("with preset importType", () => { - const importType: MapImportType = new Map(); - importType.set("@duplojs/server-utils/file", ["FileInterface"]); - - expect( - render( - SDP.file(), - { - identifier: "File", - transformers: defaultTransformers, - mode: "in", - importType, - }, - ), - ).toMatchSnapshot(); - }); }); diff --git a/tests/toTypescript/transfomers/templateLiteral.test.ts b/tests/toTypescript/transfomers/templateLiteral.test.ts index 25edf7c..55c1c8c 100644 --- a/tests/toTypescript/transfomers/templateLiteral.test.ts +++ b/tests/toTypescript/transfomers/templateLiteral.test.ts @@ -87,7 +87,7 @@ describe("templateLiteral", () => { buildError: () => E.left("buildDataParserError"), mode: "out", context: new Map(), - importType: new Map(), + importContext: new Map(), addImport(path, typeName) { return; }, @@ -111,7 +111,7 @@ describe("templateLiteral", () => { buildError: () => E.left("buildDataParserError"), mode: "out", context: new Map(), - importType: new Map(), + importContext: new Map(), addImport(path, typeName) { return; }, diff --git a/tests/toTypescript/transfomers/time.test.ts b/tests/toTypescript/transfomers/time.test.ts index 7833df5..b6461b7 100644 --- a/tests/toTypescript/transfomers/time.test.ts +++ b/tests/toTypescript/transfomers/time.test.ts @@ -1,5 +1,5 @@ import { DPE } from "@duplojs/utils"; -import { defaultTransformers, type MapImportType, render } from "@scripts/toTypescript"; +import { defaultTransformers, type MapImportContext, render } from "@scripts/toTypescript"; describe("time", () => { it("mode out", () => { @@ -54,9 +54,9 @@ describe("time", () => { ).toMatchSnapshot(); }); - it("with preset importType", () => { - const importType: MapImportType = new Map(); - importType.set("@duplojs/utils/date", ["TheTime"]); + it("with preset importContext", () => { + const importContext: MapImportContext = new Map(); + importContext.set("@duplojs/utils/date", { direct: ["TheTime"] }); expect( render( @@ -65,7 +65,7 @@ describe("time", () => { identifier: "Time", transformers: defaultTransformers, mode: "in", - importType, + importContext, }, ), ).toMatchSnapshot(); diff --git a/vitest.config.js b/vitest.config.js index 70d35f4..3f14171 100644 --- a/vitest.config.js +++ b/vitest.config.js @@ -5,6 +5,7 @@ export default defineConfig({ test: { watch: false, globals: true, + setupFiles: "tests/_utils/setup.ts", include: [ "tests/**/*.test.ts", "integration/**/*.test.ts", @@ -15,7 +16,9 @@ export default defineConfig({ reportsDirectory: "coverage", include: [ "scripts/toTypescript/transformer/defaults", - "scripts/toJsonSchema/transformer/defaults" + "scripts/toJsonSchema/transformer/defaults", + "scripts/toDataParser/checkerTransformer/defaults", + "scripts/toDataParser/dataParserTransformer/defaults" ], exclude: [ "**/*.test.ts",