Skip to content

Commit

Permalink
Merge pull request #864 from samchon/features/tags
Browse files Browse the repository at this point in the history
Gather compilation error of custom type tags too.
  • Loading branch information
samchon authored Nov 12, 2023
2 parents 9bbd297 + 8f74099 commit bb769d0
Show file tree
Hide file tree
Showing 116 changed files with 7,430 additions and 3,322 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "typia",
"version": "5.2.6",
"version": "5.2.7-dev.20231109",
"description": "Superfast runtime validators with only one line",
"main": "lib/index.js",
"typings": "lib/index.d.ts",
Expand Down
2 changes: 1 addition & 1 deletion packages/typescript-json/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "typescript-json",
"version": "5.2.6",
"version": "5.2.7-dev.20231109",
"description": "Superfast runtime validators with only one line",
"main": "lib/index.js",
"typings": "lib/index.d.ts",
Expand Down
25 changes: 12 additions & 13 deletions src/factories/ExpressionFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,9 +133,7 @@ export namespace ExpressionFactory {
);

export const transpile =
(context: ts.TransformationContext) =>
(script: string) =>
(input: ts.Expression): ts.Expression => {
(context: ts.TransformationContext) => (script: string) => {
const file: ts.SourceFile = ts.createSourceFile(
`${RandomGenerator.uuid()}.ts`,
script,
Expand All @@ -152,16 +150,17 @@ export namespace ExpressionFactory {
throw new TypeError(
"Error on ExpressionFactory.transpile(): statement is not an expression statement.",
);

const visitor = (node: ts.Node): ts.Node => {
if (ts.isIdentifier(node) && node.text === "$input")
return input;
return ts.visitEachChild(
(ts.factory as any).cloneNode(node),
visitor,
context,
);
return (input: ts.Expression): ts.Expression => {
const visitor = (node: ts.Node): ts.Node => {
if (ts.isIdentifier(node) && node.text === "$input")
return input;
return ts.visitEachChild(
(ts.factory as any).cloneNode(node),
visitor,
context,
);
};
return visitor(statement.expression) as ts.Expression;
};
return visitor(statement.expression) as ts.Expression;
};
}
7 changes: 5 additions & 2 deletions src/factories/JsonMetadataFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,13 @@ import { MetadataFactory } from "./MetadataFactory";
export namespace JsonMetadataFactory {
export const analyze =
(method: string) =>
(checker: ts.TypeChecker) =>
(checker: ts.TypeChecker, context?: ts.TransformationContext) =>
(type: ts.Type): [MetadataCollection, Metadata] => {
const collection = new MetadataCollection();
const result = MetadataFactory.analyze(checker)({
const result = MetadataFactory.analyze(
checker,
context,
)({
escape: true,
constant: true,
absorb: true,
Expand Down
2 changes: 2 additions & 0 deletions src/factories/LiteralFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ export namespace LiteralFactory {
else if (typeof input === "number") return generate_value(input);
else if (typeof input === "bigint") return generate_bigint(input);
// unreachable code
else if (typeof input === "function")
return ts.factory.createIdentifier("undefined");
else
throw new TypeError(
"Error on LiteralFactory.generate(): unknown type.",
Expand Down
66 changes: 48 additions & 18 deletions src/factories/MetadataFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { iterate_metadata_sort } from "./internal/metadata/iterate_metadata_sort

import { ValidationPipe } from "../typings/ValidationPipe";

import { ExpressionFactory } from "./ExpressionFactory";
import { MetadataCollection } from "./MetadataCollection";

export namespace MetadataFactory {
Expand Down Expand Up @@ -40,7 +41,7 @@ export namespace MetadataFactory {
}

export const analyze =
(checker: ts.TypeChecker) =>
(checker: ts.TypeChecker, context?: ts.TransformationContext) =>
(options: IOptions) =>
(collection: MetadataCollection) =>
(type: ts.Type | null): ValidationPipe<Metadata, IError> => {
Expand All @@ -59,7 +60,9 @@ export namespace MetadataFactory {
iterate_metadata_sort(collection)(meta);

if (options.validate)
errors.push(...validate(options)(options.validate)(meta));
errors.push(
...validate(context)(options)(options.validate)(meta),
);
return errors.length
? {
success: false,
Expand All @@ -84,6 +87,7 @@ export namespace MetadataFactory {
};

const validate =
(context?: ts.TransformationContext) =>
(options: IOptions) =>
(functor: Validator) =>
(meta: Metadata): IError[] => {
Expand All @@ -95,7 +99,7 @@ export namespace MetadataFactory {
tuples: new Set(),
aliases: new Set(),
};
validateMeta(options)(visitor)(meta, {
validateMeta(context)(options)(visitor)(meta, {
object: null,
property: null,
nested: null,
Expand All @@ -107,83 +111,109 @@ export namespace MetadataFactory {
};

const validateMeta =
(context?: ts.TransformationContext) =>
(options: IOptions) =>
(visitor: IValidationVisitor) =>
(meta: Metadata, explore: IExplore) => {
const result: Set<string> = new Set(visitor.functor(meta, explore));
if (result.size)
const result: string[] = [];
if (context !== undefined)
for (const atomic of meta.atomics)
for (const row of atomic.tags)
for (const tag of row.filter(
(t) =>
t.validate !== undefined &&
t.predicate === undefined,
))
try {
tag.predicate = ExpressionFactory.transpile(
context,
)(tag.validate!);
} catch {
result.push(
`Unable to transpile type tag script: ${JSON.stringify(
tag.validate,
)}`,
);
tag.predicate = () => ts.factory.createTrue();
}
result.push(...visitor.functor(meta, explore));
if (result.length)
visitor.errors.push({
name: meta.getName(),
explore: { ...explore },
messages: [...result],
messages: [...new Set(result)],
});

for (const alias of meta.aliases)
validateAlias(options)(visitor)(alias, explore);
validateAlias(context)(options)(visitor)(alias, explore);
for (const array of meta.arrays)
validateArray(options)(visitor)(array.type, explore);
validateArray(context)(options)(visitor)(array.type, explore);
for (const tuple of meta.tuples)
validateTuple(options)(visitor)(tuple.type, explore);
validateTuple(context)(options)(visitor)(tuple.type, explore);
for (const obj of meta.objects)
validateObject(options)(visitor)(obj);
validateObject(context)(options)(visitor)(obj);
for (const set of meta.sets)
validateMeta(options)(visitor)(set, explore);
validateMeta(context)(options)(visitor)(set, explore);
for (const map of meta.maps) {
validateMeta(options)(visitor)(map.key, explore);
validateMeta(options)(visitor)(map.value, explore);
validateMeta(context)(options)(visitor)(map.key, explore);
validateMeta(context)(options)(visitor)(map.value, explore);
}

if (options.escape === true && meta.escaped !== null)
validateMeta(options)(visitor)(meta.escaped.returns, {
validateMeta(context)(options)(visitor)(meta.escaped.returns, {
...explore,
escaped: true,
});
};

const validateAlias =
(context?: ts.TransformationContext) =>
(options: IOptions) =>
(visitor: IValidationVisitor) =>
(alias: MetadataAlias, explore: IExplore) => {
if (visitor.aliases.has(alias)) return;
visitor.aliases.add(alias);

validateMeta(options)(visitor)(alias.value, {
validateMeta(context)(options)(visitor)(alias.value, {
...explore,
nested: alias,
aliased: true,
});
};

const validateArray =
(context?: ts.TransformationContext) =>
(options: IOptions) =>
(visitor: IValidationVisitor) =>
(array: MetadataArrayType, explore: IExplore) => {
if (visitor.arrays.has(array)) return;
visitor.arrays.add(array);

validateMeta(options)(visitor)(array.value, {
validateMeta(context)(options)(visitor)(array.value, {
...explore,
nested: array,
top: false,
});
};

const validateTuple =
(context?: ts.TransformationContext) =>
(options: IOptions) =>
(visitor: IValidationVisitor) =>
(tuple: MetadataTupleType, explore: IExplore) => {
if (visitor.tuples.has(tuple)) return;
visitor.tuples.add(tuple);

for (const elem of tuple.elements)
validateMeta(options)(visitor)(elem, {
validateMeta(context)(options)(visitor)(elem, {
...explore,
nested: tuple,
top: false,
});
};

const validateObject =
(context?: ts.TransformationContext) =>
(options: IOptions) =>
(visitor: IValidationVisitor) =>
(object: MetadataObject) => {
Expand Down Expand Up @@ -215,7 +245,7 @@ export namespace MetadataFactory {
}

for (const property of object.properties)
validateMeta(options)(visitor)(property.value, {
validateMeta(context)(options)(visitor)(property.value, {
object,
property: property.key.isSoleLiteral()
? property.key.getSoleLiteral()!
Expand Down
73 changes: 56 additions & 17 deletions src/factories/NumericRangeFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,28 +6,67 @@ import { ExpressionFactory } from "./ExpressionFactory";

export namespace NumericRangeFactory {
export const number =
(context: ts.TransformationContext) =>
(type: ProtobufAtomic.Numeric) =>
(input: ts.Expression): ts.Expression =>
ExpressionFactory.transpile(context)(NUMBER_TEXT[type])(input);
NumberPredicator[type](input);

export const bigint =
(context: ts.TransformationContext) =>
(type: ProtobufAtomic.BigNumeric) =>
(input: ts.Expression): ts.Expression =>
ExpressionFactory.transpile(context)(BIGINT_TEXT[type])(input);
BigIntPredicator[type](input);
}

const NUMBER_TEXT = {
int32: `Math.floor($input) === $input && -2147483648 <= $input && $input <= 2147483647`,
uint32: `Math.floor($input) === $input && 0 <= $input && $input <= 4294967295`,
int64: `Math.floor($input) === $input && -9223372036854775808 <= $input && $input <= 9223372036854775807`,
uint64: `Math.floor($input) === $input && 0 <= $input && $input <= 18446744073709551615`,
float: `-1.175494351e38 <= $input && $input <= 3.4028235e38`,
double: `true`,
};

const BIGINT_TEXT = {
int64: `true`,
uint64: `BigInt(0) <= $input`,
};
namespace NumberPredicator {
export const int32 = (input: ts.Expression) =>
ts.factory.createLogicalAnd(
integer(input),
between("-2147483648", "2147483647")(input),
);
export const uint32 = (input: ts.Expression) =>
ts.factory.createLogicalAnd(
integer(input),
between("0", "4294967295")(input),
);
export const int64 = (input: ts.Expression) =>
ts.factory.createLogicalAnd(
integer(input),
between("-9223372036854775808", "9223372036854775807")(input),
);
export const uint64 = (input: ts.Expression) =>
ts.factory.createLogicalAnd(
integer(input),
between("0", "18446744073709551615")(input),
);
export const float = (input: ts.Expression) =>
between("-1.175494351e38", "3.4028235e38")(input);
export const double = () => ts.factory.createTrue();
}

namespace BigIntPredicator {
export const int64 = () => ts.factory.createTrue();
export const uint64 = (input: ts.Expression) =>
ts.factory.createLessThanEquals(
ts.factory.createCallExpression(
ts.factory.createIdentifier("BigInt"),
undefined,
[ExpressionFactory.number(0)],
),
input,
);
}

const integer = (input: ts.Expression) =>
ts.factory.createStrictEquality(
ts.factory.createCallExpression(
ts.factory.createIdentifier("Math.floor"),
undefined,
[input],
),
input,
);

const between = (x: string, y: string) => (input: ts.Expression) =>
ts.factory.createLogicalAnd(
ts.factory.createLessThanEquals(ts.factory.createIdentifier(x), input),
ts.factory.createLessThanEquals(input, ts.factory.createIdentifier(y)),
);
7 changes: 5 additions & 2 deletions src/factories/ProtobufFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,15 @@ import { MetadataFactory } from "./MetadataFactory";
export namespace ProtobufFactory {
export const metadata =
(method: string) =>
(checker: ts.TypeChecker) =>
(checker: ts.TypeChecker, context?: ts.TransformationContext) =>
(collection: MetadataCollection) =>
(type: ts.Type): Metadata => {
// COMPOSE METADATA WITH INDIVIDUAL VALIDATIONS
const result: ValidationPipe<Metadata, MetadataFactory.IError> =
MetadataFactory.analyze(checker)({
MetadataFactory.analyze(
checker,
context,
)({
escape: false,
constant: true,
absorb: true,
Expand Down
Loading

0 comments on commit bb769d0

Please sign in to comment.