Skip to content

Commit

Permalink
Remove transformations for now, allow Check to accept functions
Browse files Browse the repository at this point in the history
  • Loading branch information
GoogleFeud committed Mar 2, 2024
1 parent b8366ba commit 81ec976
Show file tree
Hide file tree
Showing 9 changed files with 64 additions and 155 deletions.
5 changes: 1 addition & 4 deletions playground/utils/transpile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ interface ValidationError {
type NoCheck<T> = T & { __$name?: "NoCheck" };
type ExactProps<Obj extends object, removeExcessive = false, useDeleteOperator = false> = Obj & { __$type?: Obj, __$removeExcessive?: removeExcessive, __$useDeleteOprerator?: useDeleteOperator, __$name?: "ExactProps" };
type Expr<Expression extends string> = { __$type?: Expression, __$name?: "Expr" };
type Check<Cond extends string, Err extends string = never, ID extends string = any, Value extends string|number = any> = unknown & { __$check?: Cond, __$error?: Err, __$value?: Value, __$id?: ID, __$name?: "Check" };
type Check<Cond extends string | ((value: unknown) => any), Err extends string = never, ID extends string = any, Value extends string | number = any> = unknown & { __$check?: Cond, __$error?: Err, __$value?: Value, __$id?: ID, __$name?: "Check" };
type Min<T extends string | number> = number & Check<\`$self >= \${T}\`, \`to be greater than \${T}\`, "min", T>;
type Max<T extends string | number> = number & Check<\`$self <= \${T}\`, \`to be less than \${T}\`, "max", T>;
type Float = number & Check<"$self % 1 !== 0", "to be a float", "float">;
Expand All @@ -31,13 +31,10 @@ type Not<T extends Check<string, string>> = Check<\`!(\${T["__$check"]})\`, \`no
type Or<L extends Check<string, string>, R extends Check<string, string>> = Check<\`\${L["__$check"]} || \${R["__$check"]}\`, \`\${L["__$error"]} or \${R["__$error"]}\`>;
type Infer<Type> = Type & { __$name?: "Infer" };
type Resolve<Type> = Type & { __$name?: "Resolve" };
type Transform<Fns extends ((value: any) => unknown)[]> = Fns extends [...((value: any) => unknown)[], infer Last] ? Last extends (value: any) => unknown ? { __$transform?: Fns, __$return?: ReturnType<Last> } : unknown : unknown;
type Transformed<T> = { [Key in keyof T]: T[Key] extends { __$return?: unknown } ? Exclude<T[Key]["__$return"], undefined> : T[Key] };
declare function is<T, _M = { __$marker: "is" }>(prop: unknown) : prop is T;
declare function check<T, _rawErrorData extends boolean = false, _M = { __$marker: "check" }>(prop: unknown) : [T, Array<_rawErrorData extends true ? ValidationError : string>];
declare function createMatch<R, U = unknown, _M = { __$marker: "createMatch" }>(fns: ((val: any) => R)[], noDiscriminatedObjAssert?: boolean) : (val: U) => R;
declare function transform<T, _M = { __$marker: "transform" }>(value: T): Transformed<T>;
`;

export const CompilerOptions: ts.CompilerOptions = {
Expand Down
1 change: 0 additions & 1 deletion src/gen/jsonSchema/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,6 @@ export function validatorToJSONSchema(validator: Validator) : JSONSchema|undefin
case TypeDataKinds.Resolve:
case TypeDataKinds.Symbol:
case TypeDataKinds.Undefined:
case TypeDataKinds.Transformation:
return undefined;
}
}
Expand Down
30 changes: 24 additions & 6 deletions src/gen/nodes/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import ts, { isNumericLiteral } from "typescript";
import { ObjectTypeDataExactOptions, TypeDataKinds, Validator, genValidator } from "../validators";
import { CheckTypeData, ObjectTypeDataExactOptions, TypeDataKinds, Validator, genValidator } from "../validators";
import { _and, _bin, _bin_chain, _for, _if, _new, _not, _num, _or, _str, _throw, _typeof_cmp, BlockLike, UNDEFINED, concat, joinElements, _if_nest, _instanceof, _access, _call, _for_in, _ident, _bool, _obj_check, _obj, _obj_binding_decl, _arr_binding_decl, _concise, _ternary, _arr_check, _var } from "../expressionUtils";
import { Transformer } from "../../transformer";
import { isSingleIfStatement } from "../../utils";
Expand All @@ -19,18 +19,19 @@ export interface NodeGenContext {
recursiveFns: ts.FunctionDeclaration[],
recursiveFnNames: Map<ts.Type, ts.Identifier>,
resolvedTypeArguments: Map<ts.Type, Validator>,
usedErrorClases?: ts.Symbol[],
origin: ts.Node,
useElse?: boolean,
}

export function createContext(transformer: Transformer, resultType: ValidationResultType, useElse?: boolean): NodeGenContext {
export function createContext(transformer: Transformer, resultType: ValidationResultType, origin: ts.Node, useElse?: boolean): NodeGenContext {
return {
transformer,
resultType,
useElse,
recursiveFns: [],
recursiveFnNames: new Map(),
resolvedTypeArguments: new Map()
resolvedTypeArguments: new Map(),
origin
};
}

Expand Down Expand Up @@ -205,8 +206,7 @@ export function genNode(validator: Validator, ctx: NodeGenContext): GenResult {
before = first.before;
errorMsg = first.error;
}
const parseCtx = genCheckCtx(validator);
for (const check of validator.typeData.expressions) normalChecks.push(_not(ctx.transformer.stringToNode(check, parseCtx)));
normalChecks.push(...genChecks(validator.typeData.expressions, validator, ctx, true));
return {
condition: _or(normalChecks),
after,
Expand Down Expand Up @@ -356,6 +356,24 @@ export function genNode(validator: Validator, ctx: NodeGenContext): GenResult {
}
}

export function genChecks(checks: CheckTypeData["expressions"], validator: Validator, ctx: NodeGenContext, negate?: boolean) : ts.Expression[] {
const result = [];
let parseCtx;
for (const check of checks) {
if (typeof check === "string") {
if (!parseCtx) parseCtx = genCheckCtx(validator);
const value = ctx.transformer.stringToNode(check, parseCtx);
result.push(negate ? _not(value) : value);
} else {
const importedSym = ctx.transformer.importSymbol(check, ctx.origin);
if (!importedSym) continue;
const value = _call(importedSym, [validator.expression()]);
result.push(negate ? _not(value) : value);
}
}
return result;
}

export function isNullableNode(validator: Validator): ts.Expression {
return _bin(validator.expression(), UNDEFINED, ts.SyntaxKind.ExclamationEqualsEqualsToken);
}
Expand Down
7 changes: 3 additions & 4 deletions src/gen/nodes/match.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import ts from "typescript";
import { Transformer } from "../../transformer";
import { TypeDataKinds, Validator, genValidator } from "../validators";
import { UNDEFINED, _access, _and, _arr_check, _arrow_fn, _bin, _bool, _call, _ident, _not, _obj_check, _or, BlockLike, _if_chain, _var } from "../expressionUtils";
import { GenResult, NodeGenContext, createContext, genCheckCtx, genNode, getUnionMembers } from ".";
import { GenResult, NodeGenContext, createContext, genChecks, genNode, getUnionMembers } from ".";
import { doesAlwaysReturn } from "../../utils";

export interface MatchArm {
Expand Down Expand Up @@ -53,8 +53,7 @@ export function genConciseNode(validator: Validator, ctx: NodeGenContext, genBas
};
}
case TypeDataKinds.Check: {
const parseCtx = genCheckCtx(validator);
const checks = validator.typeData.expressions.map(check => ctx.transformer.stringToNode(check, parseCtx));
const checks = genChecks(validator.typeData.expressions, validator, ctx, false);
const child = validator.children[0];
if (child) {
if (child.isComplexType()) checks.unshift(genConciseNode(child, ctx, genBaseCheck).condition);
Expand Down Expand Up @@ -155,7 +154,7 @@ export function genMatch(transformer: Transformer, functionTuple: ts.ArrayLitera
}
}

const ctx = createContext(transformer, {});
const ctx = createContext(transformer, {}, functionTuple);

// Objects will always get checked last, otherwise sort by weight
const sortedTypeGroups = [...typeGroups.entries()].sort((a, b) => {
Expand Down
65 changes: 0 additions & 65 deletions src/gen/transformation/index.ts

This file was deleted.

53 changes: 27 additions & 26 deletions src/gen/validators/genValidator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,35 +36,26 @@ export function genValidator(transformer: Transformer, type: ts.Type | undefined
}
else if (type.getCallSignatures().length === 1) return new Validator(type, name, { kind: TypeDataKinds.Function }, exp, parent);
else {
if (type.getProperty("__$transform")) {
if (!transforms) return;
const transformerFn = type.isIntersection() ? type.types.find(t => t.getProperty("__$transform")) : type;
if (!transformerFn) return;
const resolved = getMappedRawType(transformerFn, 0);
if (!resolved || !transformer.checker.isTupleType(resolved)) return;
return new Validator(type, name, {
kind: TypeDataKinds.Transformation,
transformers: transformer.checker.getTypeArguments(resolved as ts.TypeReference).map(t => t.symbol)
}, exp, parent);
}
// Check has precedence over other utility types
if (type.getProperty("__$check")) {
if (type.isIntersection()) {
const checks = [], hints: CheckTypeHint[] = [];
const checks: (string | ts.Symbol)[] = [], hints: CheckTypeHint[] = [];
let firstNonCheckType: ts.Type|undefined;
for (const innerType of type.types) {
const typeWithMapper = innerType as (ts.Type & { mapper: ts.TypeMapper });
const isUtilityType = transformer.getPropType(typeWithMapper, "name");
if (isUtilityType && isUtilityType.isStringLiteral() && !typeWithMapper.getProperty("__$check")) firstNonCheckType = typeWithMapper;
else if (typeWithMapper.mapper) {
if (typeWithMapper.mapper.kind === ts.TypeMapKind.Simple && typeWithMapper.mapper.target.isStringLiteral()) {
checks.push(typeWithMapper.mapper.target.value);
if (typeWithMapper.mapper.kind === ts.TypeMapKind.Simple) {
const target = typeWithMapper.mapper.target;
if (target.isStringLiteral()) checks.push(target.value);
else if (target.symbol) checks.push(target.symbol);
continue;
}
else {
const exp = getMappedType(typeWithMapper, 0);
if (typeof exp === "string") checks.push(exp);
else continue;
const exp = resolveCheck(typeWithMapper, 0);
if (!exp) continue;
checks.push(exp);
hints.push({
error: getMappedType(typeWithMapper, 1) as string|undefined,
name: getMappedType(typeWithMapper, 2) as string|undefined,
Expand All @@ -81,9 +72,9 @@ export function genValidator(transformer: Transformer, type: ts.Type | undefined
if (typeWithMapper.mapper) {
if (typeWithMapper.mapper.kind === ts.TypeMapKind.Simple && typeWithMapper.mapper.target.isStringLiteral()) check = typeWithMapper.mapper.target.value;
else {
const exp = getMappedType(typeWithMapper, 0);
if (typeof exp === "string") check = exp;
else return;
const exp = resolveCheck(typeWithMapper, 0);
if (!exp) return;
check = exp;
hint = {
error: getMappedType(typeWithMapper, 1) as string|undefined,
name: getMappedType(typeWithMapper, 2) as string|undefined,
Expand Down Expand Up @@ -184,18 +175,28 @@ export function genValidator(transformer: Transformer, type: ts.Type | undefined
}
}

export function resolveCheck(type: ts.Type, index: number) : string | ts.Symbol | undefined {
const mappedVal = getMappedRawType(type, index);
if (!mappedVal) return;
if (mappedVal.isStringLiteral()) return mappedVal.value;
else if (mappedVal.symbol) return mappedVal.symbol;
else return undefined;
}

export function getMappedRawType(type: ts.Type, index: number) : ts.Type | undefined {
if (!("mapper" in type)) return;
const typeMapper = (type as (ts.Type & { mapper: ts.TypeMapper })).mapper;
if (typeMapper.kind === ts.TypeMapKind.Simple) return typeMapper.target;
else if (typeMapper.kind === ts.TypeMapKind.Array && typeMapper.targets) return typeMapper.targets[index];
else return;
let mappedType;
if (typeMapper.kind === ts.TypeMapKind.Simple) mappedType = typeMapper.target;
else if (typeMapper.kind === ts.TypeMapKind.Array && typeMapper.targets) mappedType = typeMapper.targets[index];

if (!mappedType) return;
if (mappedType.isUnion()) return mappedType.types[mappedType.types.length - 1];
return mappedType;
}

export function getMappedType(type: ts.Type, index: number) : string|number|undefined {
let rawType = getMappedRawType(type, index);
if (!rawType) return;
if (rawType.isUnion()) rawType = rawType.types[rawType.types.length - 1];
const rawType = getMappedRawType(type, index);
if (!rawType || !rawType.isLiteral()) return;
return rawType.value as string | number;
}
12 changes: 3 additions & 9 deletions src/gen/validators/validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@ export enum TypeDataKinds {
Union,
Resolve,
Recursive,
Check,
Transformation
Check
}

export interface CheckTypeHint {
Expand All @@ -30,7 +29,7 @@ export interface CheckTypeHint {

export interface CheckTypeData {
kind: TypeDataKinds.Check,
expressions: string[],
expressions: (string | ts.Symbol)[],
hints: CheckTypeHint[]
}

Expand Down Expand Up @@ -112,12 +111,7 @@ export interface ObjectTypeData {
couldBeNull?: boolean
}

export interface TransformationTypeData {
kind: TypeDataKinds.Transformation,
transformers: ts.Symbol[]
}

export type TypeData = BooleanTypeData | SymbolTypeData | FunctionTypeData | UnionTypeData | ClassTypeData | BigIntTypeData | NullTypeData | TupleTypeData | NumberTypeData | StringTypeData | ArrayTypeData | ObjectTypeData | UndefinedTypeData | ResolveTypeData | RecursiveTypeData | CheckTypeData | TransformationTypeData;
export type TypeData = BooleanTypeData | SymbolTypeData | FunctionTypeData | UnionTypeData | ClassTypeData | BigIntTypeData | NullTypeData | TupleTypeData | NumberTypeData | StringTypeData | ArrayTypeData | ObjectTypeData | UndefinedTypeData | ResolveTypeData | RecursiveTypeData | CheckTypeData;

export type ValidatorTargetName = string | number | ts.Identifier;

Expand Down
Loading

0 comments on commit 81ec976

Please sign in to comment.